D2777: wireproto: raise ProgrammingError instead of Abort

2018-03-10 Thread indygreg (Gregory Szorc)
This revision was automatically updated to reflect the committed changes.
Closed by commit rHGef68493d652b: wireproto: raise ProgrammingError instead of 
Abort (authored by indygreg, committed by ).

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2777?vs=6823&id=6867

REVISION DETAIL
  https://phab.mercurial-scm.org/D2777

AFFECTED FILES
  mercurial/wireproto.py

CHANGE DETAILS

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -704,12 +704,13 @@
 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
   if v['version'] == 2}
 else:
-raise error.Abort(_('invalid transport policy value: %s') %
-  transportpolicy)
+raise error.ProgrammingError('invalid transport policy value: %s' %
+ transportpolicy)
 
 if permission not in ('push', 'pull'):
-raise error.Abort(_('invalid wire protocol permission; got %s; '
-'expected "push" or "pull"') % permission)
+raise error.ProgrammingError('invalid wire protocol permission; '
+ 'got %s; expected "push" or "pull"' %
+ permission)
 
 def register(func):
 commands[name] = commandentry(func, args=args, transports=transports,



To: indygreg, #hg-reviewers, pulkit
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH 1 of 5] py3: use r'' instead of sysstr('') to get around code transformer

2018-03-10 Thread Pulkit Goyal
On Sat, Mar 10, 2018 at 4:58 PM, Yuya Nishihara  wrote:
> # HG changeset patch
> # User Yuya Nishihara 
> # Date 1520665036 -32400
> #  Sat Mar 10 15:57:16 2018 +0900
> # Node ID fca950cc0de8a69d23bf37521e43cc5310009aad
> # Parent  ebabfc339ee4fe88ae7ff96bc925d66512066da0
> py3: use r'' instead of sysstr('') to get around code transformer

Queued the series. Many thanks!
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH] ui: remove any combinations of CR|LF from prompt response

2018-03-10 Thread Pulkit Goyal
On Sat, Mar 10, 2018 at 10:37 PM, Matt Harbison  wrote:
> On Sat, 10 Mar 2018 02:52:00 -0500, Yuya Nishihara  wrote:
>
>> # HG changeset patch
>> # User Yuya Nishihara 
>> # Date 1520664609 -32400
>> #  Sat Mar 10 15:50:09 2018 +0900
>> # Node ID c38b2b364df79a9defc3520f19207ce47abcc7d8
>> # Parent  9ddc9aa26801bac571bd3413a8aed900c2d2efb8
>> ui: remove any combinations of CR|LF from prompt response
>
>
> I didn't run the whole test suite, but this fixes the ones that broke.
> Thanks.

Queued as per Matt review. Thanks.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2801: hgweb: don't redundantly pass templater with requestcontext (API)

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  The requestcontenxt has a ``tmpl`` attribute to access the
  templater. We don't need to pass the templater explicitly when
  passing a requestcontext instance.
  
  .. api::
  
Various helper functions in hgweb.webutil no longer accept a
templater instance. Access the templater through the
``web`` argument instead.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2801

AFFECTED FILES
  mercurial/hgweb/webcommands.py
  mercurial/hgweb/webutil.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -389,17 +389,17 @@
 'child': lambda **x: children(ctx),
 }
 
-def changelistentry(web, ctx, tmpl):
+def changelistentry(web, ctx):
 '''Obtain a dictionary to be used for entries in a changelist.
 
 This function is called when producing items for the "entries" list passed
 to the "shortlog" and "changelog" templates.
 '''
 repo = web.repo
 rev = ctx.rev()
 n = ctx.node()
-showtags = showtag(repo, tmpl, 'changelogtag', n)
-files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
+showtags = showtag(repo, web.tmpl, 'changelogtag', n)
+files = listfilediffs(web.tmpl, ctx.files(), n, web.maxfiles)
 
 entry = commonentry(repo, ctx)
 entry.update(
@@ -417,21 +417,21 @@
 else:
 return short(ctx.node())
 
-def changesetentry(web, req, tmpl, ctx):
+def changesetentry(web, req, ctx):
 '''Obtain a dictionary to be used to render the "changeset" template.'''
 
-showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
-showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
+showtags = showtag(web.repo, web.tmpl, 'changesettag', ctx.node())
+showbookmarks = showbookmark(web.repo, web.tmpl, 'changesetbookmark',
  ctx.node())
 showbranch = nodebranchnodefault(ctx)
 
 files = []
 parity = paritygen(web.stripecount)
 for blockno, f in enumerate(ctx.files()):
 template = 'filenodelink' if f in ctx else 'filenolink'
-files.append(tmpl(template,
-  node=ctx.hex(), file=f, blockno=blockno + 1,
-  parity=next(parity)))
+files.append(web.tmpl(template,
+  node=ctx.hex(), file=f, blockno=blockno + 1,
+  parity=next(parity)))
 
 basectx = basechangectx(web.repo, req)
 if basectx is None:
@@ -441,11 +441,11 @@
 if 'style' in req.req.qsparams:
 style = req.req.qsparams['style']
 
-diff = diffs(web, tmpl, ctx, basectx, None, style)
+diff = diffs(web, ctx, basectx, None, style)
 
 parity = paritygen(web.stripecount)
 diffstatsgen = diffstatgen(ctx, basectx)
-diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
+diffstats = diffstat(web.tmpl, ctx, diffstatsgen, parity)
 
 return dict(
 diff=diff,
@@ -466,7 +466,7 @@
 if len(files) > max:
 yield tmpl('fileellipses')
 
-def diffs(web, tmpl, ctx, basectx, files, style, linerange=None,
+def diffs(web, ctx, basectx, files, style, linerange=None,
   lineidprefix=''):
 
 def prettyprintlines(lines, blockno):
@@ -480,11 +480,12 @@
 ltype = "difflineat"
 else:
 ltype = "diffline"
-yield tmpl(ltype,
-   line=l,
-   lineno=lineno,
-   lineid=lineidprefix + "l%s" % difflineno,
-   linenumber="% 8s" % difflineno)
+yield web.tmpl(
+ltype,
+line=l,
+lineno=lineno,
+lineid=lineidprefix + "l%s" % difflineno,
+linenumber="% 8s" % difflineno)
 
 repo = web.repo
 if files:
@@ -509,8 +510,8 @@
 continue
 lines.extend(hunklines)
 if lines:
-yield tmpl('diffblock', parity=next(parity), blockno=blockno,
-   lines=prettyprintlines(lines, blockno))
+yield web.tmpl('diffblock', parity=next(parity), blockno=blockno,
+   lines=prettyprintlines(lines, blockno))
 
 def compare(tmpl, context, leftlines, rightlines):
 '''Generator function that provides side-by-side comparison data.'''
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -399,7 +399,7 @@
 if curcount > revcount + 1:
 break
 
-entry = webutil.changelistentry(web, web.repo[rev], web.tmpl)
+entry = webutil.changelistentry(web, web.repo[rev])
 entry['parity'] = next(parity)
 yield entry
 
@@ -485,7 +485,7 @@
 

D2797: hgweb: stop setting headers on wsgirequest

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  All commands now go through the new response API. This is dead code.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2797

AFFECTED FILES
  mercurial/hgweb/hgweb_mod.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -313,9 +313,6 @@
 if rctx.csp:
 # hgwebdir may have added CSP header. Since we generate our own,
 # replace it.
-wsgireq.headers = [h for h in wsgireq.headers
-   if h[0] != 'Content-Security-Policy']
-wsgireq.headers.append(('Content-Security-Policy', rctx.csp))
 res.headers['Content-Security-Policy'] = rctx.csp
 
 handled = wireprotoserver.handlewsgirequest(
@@ -393,7 +390,6 @@
 res.setbodybytes('')
 return res.sendresponse()
 
-wsgireq.headers.append((r'ETag', pycompat.sysstr(tag)))
 res.headers['ETag'] = tag
 
 if cmd not in webcommands.__all__:



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


D2804: hgweb: fix a bug due to variable name typo

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  It looks like the "sort" query string parameter was not being
  honored properly.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2804

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
@@ -481,7 +481,7 @@
 sortable = ["name", "description", "contact", "lastchange"]
 sortcolumn, descending = sortdefault
 if 'sort' in wsgireq.req.qsparams:
-sortcolum = wsgireq.req.qsparams['sort']
+sortcolumn = wsgireq.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


D2791: hgweb: refactor fake file object proxy for archiving

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Python's zip file writer operates on a file object. When doing work,
  it periodically calls write(), flush(), and tell() on that object.
  
  In WSGI contexts, the start_response function returns a write()
  function. That's a function to write data, not a full file object.
  So, when the archival code was first introduced in 
https://phab.mercurial-scm.org/rHG2b03c6733efae2a76179cfc49bd591023fb0fa3a in
  2006, someone invented a proxy "tellable" type that wrapped a file
  object like object and kept track of write count so it could
  implement tell() and satisfy zipfile's needs.
  
  When our archival code runs, it attempts to tell() the destination
  and if that fails, converts it to a "tellable" instance. Our WSGI
  application passes the "wsgirequest" instance to the archival
  function. It fails the tell() test and is converted to a "tellable."
  It's worth noting that "wsgirequest" implements flush(), so
  "tellable" doesn't.
  
  This hackery all seems very specific to the WSGI code. So this commit
  moves the "tellable" type and the conversion of the destination file
  object into the WSGI code. There's a chance some other caller may be
  passing a file object like object that doesn't implement tell(). But
  I doubt it.
  
  As part of the refactor, our new type implements flush() and doesn't
  implement __getattr__. Given the intended limited use of this type,
  I want things to fail fast if there is an attempt to access attributes
  because I think it is important to document which attributes are being
  used for what purposes.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2791

AFFECTED FILES
  mercurial/archival.py
  mercurial/hgweb/request.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -24,6 +24,9 @@
 paritygen,
 staticfile,
 )
+from . import (
+request as requestmod,
+)
 
 from .. import (
 archival,
@@ -1215,7 +1218,9 @@
 req.headers.extend(headers)
 req.respond(HTTP_OK, mimetype)
 
-archival.archive(web.repo, req, cnode, artype, prefix=name,
+bodyfh = requestmod.offsettrackingwriter(req.write)
+
+archival.archive(web.repo, bodyfh, cnode, artype, prefix=name,
  matchfn=match,
  subrepos=web.configbool("web", "archivesubrepos"))
 return []
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -290,6 +290,37 @@
  headers=headers,
  bodyfh=bodyfh)
 
+class offsettrackingwriter(object):
+"""A file object like object that is append only and tracks write count.
+
+Instances are bound to a callable. This callable is called with data
+whenever a ``write()`` is attempted.
+
+Instances track the amount of written data so they can answer ``tell()``
+requests.
+
+The intent of this class is to wrap the ``write()`` function returned by
+a WSGI ``start_response()`` function. Since ``write()`` is a callable and
+not a file object, it doesn't implement other file object methods.
+"""
+def __init__(self, writefn):
+self._write = writefn
+self._offset = 0
+
+def write(self, s):
+res = self._write(s)
+# Some Python objects don't report the number of bytes written.
+if res is None:
+self._offset += len(s)
+else:
+self._offset += res
+
+def flush(self):
+pass
+
+def tell(self):
+return self._offset
+
 class wsgiresponse(object):
 """Represents a response to a WSGI request.
 
diff --git a/mercurial/archival.py b/mercurial/archival.py
--- a/mercurial/archival.py
+++ b/mercurial/archival.py
@@ -195,34 +195,11 @@
 if self.fileobj:
 self.fileobj.close()
 
-class tellable(object):
-'''provide tell method for zipfile.ZipFile when writing to http
-response file object.'''
-
-def __init__(self, fp):
-self.fp = fp
-self.offset = 0
-
-def __getattr__(self, key):
-return getattr(self.fp, key)
-
-def write(self, s):
-self.fp.write(s)
-self.offset += len(s)
-
-def tell(self):
-return self.offset
-
 class zipit(object):
 '''write archive to zip file or stream.  can write uncompressed,
 or compressed with deflate.'''
 
 def __init__(self, dest, mtime, compress=True):
-if not isinstance(dest, bytes):
-try:
-dest.tell()
-except (AttributeError, IOError):
-dest = tellable(dest)
 self.z = zipfile.ZipFile(pycompat.fsdecode(dest), r'w',
  compress and zipfil

D2800: hgweb: use templater on requestcontext instance

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  After this commit, all @webcommand function no longer use their
  "tmpl" argument. Instead, they use the templater attached to the
  requestcontext.
  
  This is the same exact object. So there should be no difference in
  behavior.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2800

AFFECTED FILES
  hgext/highlight/__init__.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -95,23 +95,23 @@
 """
 
 if web.req.qsparams.get('file'):
-return filelog(web, req, tmpl)
+return filelog(web, req, None)
 else:
-return changelog(web, req, tmpl)
+return changelog(web, req, None)
 
 @webcommand('rawfile')
 def rawfile(web, req, tmpl):
 guessmime = web.configbool('web', 'guessmime')
 
 path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', ''))
 if not path:
-return manifest(web, req, tmpl)
+return manifest(web, req, None)
 
 try:
 fctx = webutil.filectx(web.repo, req)
 except error.LookupError as inst:
 try:
-return manifest(web, req, tmpl)
+return manifest(web, req, None)
 except ErrorResponse:
 raise inst
 
@@ -135,7 +135,7 @@
 web.res.setbodybytes(text)
 return web.res.sendresponse()
 
-def _filerevision(web, req, tmpl, fctx):
+def _filerevision(web, req, fctx):
 f = fctx.path()
 text = fctx.data()
 parity = paritygen(web.stripecount)
@@ -184,20 +184,20 @@
 be rendered.
 """
 if web.req.qsparams.get('style') == 'raw':
-return rawfile(web, req, tmpl)
+return rawfile(web, req, None)
 
 path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', ''))
 if not path:
-return manifest(web, req, tmpl)
+return manifest(web, req, None)
 try:
-return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
+return _filerevision(web, req, webutil.filectx(web.repo, req))
 except error.LookupError as inst:
 try:
-return manifest(web, req, tmpl)
+return manifest(web, req, None)
 except ErrorResponse:
 raise inst
 
-def _search(web, tmpl):
+def _search(web):
 MODE_REVISION = 'rev'
 MODE_KEYWORD = 'keyword'
 MODE_REVSET = 'revset'
@@ -290,14 +290,16 @@
 for ctx in searchfunc[0](funcarg):
 count += 1
 n = ctx.node()
-showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
-files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
+showtags = webutil.showtag(web.repo, web.tmpl, 'changelogtag', n)
+files = webutil.listfilediffs(web.tmpl, ctx.files(), n,
+  web.maxfiles)
 
-yield tmpl('searchentry',
-   parity=next(parity),
-   changelogtag=showtags,
-   files=files,
-   **pycompat.strkwargs(webutil.commonentry(web.repo, 
ctx)))
+yield web.tmpl(
+'searchentry',
+parity=next(parity),
+changelogtag=showtags,
+files=files,
+**pycompat.strkwargs(webutil.commonentry(web.repo, ctx)))
 
 if count >= revcount:
 break
@@ -308,14 +310,14 @@
 try:
 revcount = int(web.req.qsparams.get('revcount', revcount))
 revcount = max(revcount, 1)
-tmpl.defaults['sessionvars']['revcount'] = revcount
+web.tmpl.defaults['sessionvars']['revcount'] = revcount
 except ValueError:
 pass
 
-lessvars = copy.copy(tmpl.defaults['sessionvars'])
+lessvars = copy.copy(web.tmpl.defaults['sessionvars'])
 lessvars['revcount'] = max(revcount // 2, 1)
 lessvars['rev'] = query
-morevars = copy.copy(tmpl.defaults['sessionvars'])
+morevars = copy.copy(web.tmpl.defaults['sessionvars'])
 morevars['revcount'] = revcount * 2
 morevars['rev'] = query
 
@@ -382,7 +384,7 @@
 ctx = webutil.changectx(web.repo, req)
 symrev = webutil.symrevorshortnode(req, ctx)
 elif 'rev' in web.req.qsparams:
-return _search(web, tmpl)
+return _search(web)
 else:
 ctx = web.repo['tip']
 symrev = 'tip'
@@ -397,7 +399,7 @@
 if curcount > revcount + 1:
 break
 
-entry = webutil.changelistentry(web, web.repo[rev], tmpl)
+entry = webutil.changelistentry(web, web.repo[rev], web.tmpl)
 entry['parity'] = next(parity)
 yield entry
 
@@ -410,13 +412,13 @@
 try:
 revcount = int(web.req.qspara

D2799: hgweb: add a sendtemplate() helper function

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This pattern is common. Let's make a helper function to reduce
  boilerplate.
  
  We store the "global" template on the requestcontext instance and
  use it. The templater used by the helper function is the same
  templater that's passed in as an argument to the @webcommand
  functions. It needs to be this way because various commands are
  accessing and mutating the defaults on the templater instance.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2799

AFFECTED FILES
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -59,7 +59,8 @@
 
 The function returns a generator to be consumed by the WSGI application.
 For most commands, this should be the result from
-``web.res.sendresponse()``.
+``web.res.sendresponse()``. Many commands will call ``web.sendtemplate()``
+to render a template.
 
 Usage:
 
@@ -151,18 +152,16 @@
"linenumber": "% 6d" % (lineno + 1),
"parity": next(parity)}
 
-web.res.setbodygen(tmpl(
+return web.sendtemplate(
 'filerevision',
 file=f,
 path=webutil.up(f),
 text=lines(),
 symrev=webutil.symrevorshortnode(req, fctx),
 rename=webutil.renamelink(fctx),
 permissions=fctx.manifest().flags(f),
 ishead=int(ishead),
-**pycompat.strkwargs(webutil.commonentry(web.repo, fctx
-
-return web.res.sendresponse()
+**pycompat.strkwargs(webutil.commonentry(web.repo, fctx)))
 
 @webcommand('file')
 def file(web, req, tmpl):
@@ -339,7 +338,7 @@
 tip = web.repo['tip']
 parity = paritygen(web.stripecount)
 
-web.res.setbodygen(tmpl(
+return web.sendtemplate(
 'search',
 query=query,
 node=tip.hex(),
@@ -350,9 +349,7 @@
 lessvars=lessvars,
 modedesc=searchfunc[1],
 showforcekw=showforcekw,
-showunforcekw=showunforcekw))
-
-return web.res.sendresponse()
+showunforcekw=showunforcekw)
 
 @webcommand('changelog')
 def changelog(web, req, tmpl, shortlog=False):
@@ -436,7 +433,7 @@
 else:
 nextentry = []
 
-web.res.setbodygen(tmpl(
+return web.sendtemplate(
 'shortlog' if shortlog else 'changelog',
 changenav=changenav,
 node=ctx.hex(),
@@ -450,9 +447,7 @@
 revcount=revcount,
 morevars=morevars,
 lessvars=lessvars,
-query=query))
-
-return web.res.sendresponse()
+query=query)
 
 @webcommand('shortlog')
 def shortlog(web, req, tmpl):
@@ -485,9 +480,10 @@
 templates related to diffs may all be used to produce the output.
 """
 ctx = webutil.changectx(web.repo, req)
-web.res.setbodygen(tmpl('changeset',
-**webutil.changesetentry(web, req, tmpl, ctx)))
-return web.res.sendresponse()
+
+return web.sendtemplate(
+'changeset',
+**webutil.changesetentry(web, req, tmpl, ctx))
 
 rev = webcommand('rev')(changeset)
 
@@ -588,18 +584,16 @@
"emptydirs": "/".join(emptydirs),
"basename": d}
 
-web.res.setbodygen(tmpl(
+return web.sendtemplate(
 'manifest',
 symrev=symrev,
 path=abspath,
 up=webutil.up(abspath),
 upparity=next(parity),
 fentries=filelist,
 dentries=dirlist,
 archives=web.archivelist(hex(node)),
-**pycompat.strkwargs(webutil.commonentry(web.repo, ctx
-
-return web.res.sendresponse()
+**pycompat.strkwargs(webutil.commonentry(web.repo, ctx)))
 
 @webcommand('tags')
 def tags(web, req, tmpl):
@@ -628,14 +622,12 @@
"date": web.repo[n].date(),
"node": hex(n)}
 
-web.res.setbodygen(tmpl(
+return web.sendtemplate(
 'tags',
 node=hex(web.repo.changelog.tip()),
 entries=lambda **x: entries(False, False, **x),
 entriesnotip=lambda **x: entries(True, False, **x),
-latestentry=lambda **x: entries(True, True, **x)))
-
-return web.res.sendresponse()
+latestentry=lambda **x: entries(True, True, **x))
 
 @webcommand('bookmarks')
 def bookmarks(web, req, tmpl):
@@ -669,14 +661,12 @@
 else:
 latestrev = -1
 
-web.res.setbodygen(tmpl(
+return web.sendtemplate(
 'bookmarks',
 node=hex(web.repo.changelog.tip()),
 lastchange=[{'date': web.repo[latestrev].date()}],
 entries=lambda **x: entries(latestonly=False, **x),
-latestentry=lambda **x: entries(latestonly=True, **x)))
-
-return web.res.sendresponse()
+latestentry=lambda **x: entries(latestonly=True, **x))
 
 @webcommand('branches')
 def branche

D2798: hgweb: use web.req instead of req.req

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We now have access to the modern request type on the
  requestcontext instance. Let's access it from there.
  
  While we're here, remove an unused argument from _search().

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2798

AFFECTED FILES
  mercurial/hgweb/webcommands.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -93,16 +93,16 @@
 file will be shown. This form is equivalent to the ``filelog`` handler.
 """
 
-if req.req.qsparams.get('file'):
+if web.req.qsparams.get('file'):
 return filelog(web, req, tmpl)
 else:
 return changelog(web, req, tmpl)
 
 @webcommand('rawfile')
 def rawfile(web, req, tmpl):
 guessmime = web.configbool('web', 'guessmime')
 
-path = webutil.cleanpath(web.repo, req.req.qsparams.get('file', ''))
+path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', ''))
 if not path:
 return manifest(web, req, tmpl)
 
@@ -187,7 +187,7 @@
 if web.req.qsparams.get('style') == 'raw':
 return rawfile(web, req, tmpl)
 
-path = webutil.cleanpath(web.repo, req.req.qsparams.get('file', ''))
+path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', ''))
 if not path:
 return manifest(web, req, tmpl)
 try:
@@ -198,7 +198,7 @@
 except ErrorResponse:
 raise inst
 
-def _search(web, req, tmpl):
+def _search(web, tmpl):
 MODE_REVISION = 'rev'
 MODE_KEYWORD = 'keyword'
 MODE_REVSET = 'revset'
@@ -303,11 +303,11 @@
 if count >= revcount:
 break
 
-query = req.req.qsparams['rev']
+query = web.req.qsparams['rev']
 revcount = web.maxchanges
-if 'revcount' in req.req.qsparams:
+if 'revcount' in web.req.qsparams:
 try:
-revcount = int(req.req.qsparams.get('revcount', revcount))
+revcount = int(web.req.qsparams.get('revcount', revcount))
 revcount = max(revcount, 1)
 tmpl.defaults['sessionvars']['revcount'] = revcount
 except ValueError:
@@ -322,7 +322,7 @@
 
 mode, funcarg = getsearchmode(query)
 
-if 'forcekw' in req.req.qsparams:
+if 'forcekw' in web.req.qsparams:
 showforcekw = ''
 showunforcekw = searchfuncs[mode][1]
 mode = MODE_KEYWORD
@@ -381,11 +381,11 @@
 """
 
 query = ''
-if 'node' in req.req.qsparams:
+if 'node' in web.req.qsparams:
 ctx = webutil.changectx(web.repo, req)
 symrev = webutil.symrevorshortnode(req, ctx)
-elif 'rev' in req.req.qsparams:
-return _search(web, req, tmpl)
+elif 'rev' in web.req.qsparams:
+return _search(web, tmpl)
 else:
 ctx = web.repo['tip']
 symrev = 'tip'
@@ -409,9 +409,9 @@
 else:
 revcount = web.maxchanges
 
-if 'revcount' in req.req.qsparams:
+if 'revcount' in web.req.qsparams:
 try:
-revcount = int(req.req.qsparams.get('revcount', revcount))
+revcount = int(web.req.qsparams.get('revcount', revcount))
 revcount = max(revcount, 1)
 tmpl.defaults['sessionvars']['revcount'] = revcount
 except ValueError:
@@ -516,13 +516,13 @@
 
 The ``manifest`` template will be rendered for this handler.
 """
-if 'node' in req.req.qsparams:
+if 'node' in web.req.qsparams:
 ctx = webutil.changectx(web.repo, req)
 symrev = webutil.symrevorshortnode(req, ctx)
 else:
 ctx = web.repo['tip']
 symrev = 'tip'
-path = webutil.cleanpath(web.repo, req.req.qsparams.get('file', ''))
+path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', ''))
 mf = ctx.manifest()
 node = ctx.node()
 
@@ -806,7 +806,7 @@
 fctx = webutil.filectx(web.repo, req)
 except LookupError:
 ctx = webutil.changectx(web.repo, req)
-path = webutil.cleanpath(web.repo, req.req.qsparams['file'])
+path = webutil.cleanpath(web.repo, web.req.qsparams['file'])
 if path not in ctx.files():
 raise
 
@@ -816,8 +816,8 @@
 basectx = ctx.p1()
 
 style = web.config('web', 'style')
-if 'style' in req.req.qsparams:
-style = req.req.qsparams['style']
+if 'style' in web.req.qsparams:
+style = web.req.qsparams['style']
 
 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
 if fctx is not None:
@@ -857,13 +857,13 @@
 The ``filecomparison`` template is rendered.
 """
 ctx = webutil.changectx(web.repo, req)
-if 'file' not in req.req.qsparams:
+if 'file' not in web.req.qsparams:
 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
-path = webutil.cleanpath(web.repo, req.req.qsparams['file'])
+path = webuti

D2802: hgweb: pass modern request type into various webutil functions (API)

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Our march towards killing wsgirequest continues.
  
  .. api::
  
Various functions in hgweb.webutil now take a modern request
object instead of ``wsgirequest``.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2802

AFFECTED FILES
  hgext/highlight/__init__.py
  mercurial/hgweb/webcommands.py
  mercurial/hgweb/webutil.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -177,7 +177,7 @@
  section=section, whitespace=True)
 
 for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
-v = req.req.qsparams.get(k)
+v = req.qsparams.get(k)
 if v is not None:
 v = util.parsebool(v)
 setattr(diffopts, k, v if v is not None else True)
@@ -295,34 +295,34 @@
 
 def changectx(repo, req):
 changeid = "tip"
-if 'node' in req.req.qsparams:
-changeid = req.req.qsparams['node']
+if 'node' in req.qsparams:
+changeid = req.qsparams['node']
 ipos = changeid.find(':')
 if ipos != -1:
 changeid = changeid[(ipos + 1):]
-elif 'manifest' in req.req.qsparams:
-changeid = req.req.qsparams['manifest']
+elif 'manifest' in req.qsparams:
+changeid = req.qsparams['manifest']
 
 return changeidctx(repo, changeid)
 
 def basechangectx(repo, req):
-if 'node' in req.req.qsparams:
-changeid = req.req.qsparams['node']
+if 'node' in req.qsparams:
+changeid = req.qsparams['node']
 ipos = changeid.find(':')
 if ipos != -1:
 changeid = changeid[:ipos]
 return changeidctx(repo, changeid)
 
 return None
 
 def filectx(repo, req):
-if 'file' not in req.req.qsparams:
+if 'file' not in req.qsparams:
 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
-path = cleanpath(repo, req.req.qsparams['file'])
-if 'node' in req.req.qsparams:
-changeid = req.req.qsparams['node']
-elif 'filenode' in req.req.qsparams:
-changeid = req.req.qsparams['filenode']
+path = cleanpath(repo, req.qsparams['file'])
+if 'node' in req.qsparams:
+changeid = req.qsparams['node']
+elif 'filenode' in req.qsparams:
+changeid = req.qsparams['filenode']
 else:
 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
 try:
@@ -333,7 +333,7 @@
 return fctx
 
 def linerange(req):
-linerange = req.req.qsparams.getall('linerange')
+linerange = req.qsparams.getall('linerange')
 if not linerange:
 return None
 if len(linerange) > 1:
@@ -412,12 +412,12 @@
 return entry
 
 def symrevorshortnode(req, ctx):
-if 'node' in req.req.qsparams:
-return templatefilters.revescape(req.req.qsparams['node'])
+if 'node' in req.qsparams:
+return templatefilters.revescape(req.qsparams['node'])
 else:
 return short(ctx.node())
 
-def changesetentry(web, req, ctx):
+def changesetentry(web, ctx):
 '''Obtain a dictionary to be used to render the "changeset" template.'''
 
 showtags = showtag(web.repo, web.tmpl, 'changesettag', ctx.node())
@@ -433,13 +433,13 @@
   node=ctx.hex(), file=f, blockno=blockno + 1,
   parity=next(parity)))
 
-basectx = basechangectx(web.repo, req)
+basectx = basechangectx(web.repo, web.req)
 if basectx is None:
 basectx = ctx.p1()
 
 style = web.config('web', 'style')
-if 'style' in req.req.qsparams:
-style = req.req.qsparams['style']
+if 'style' in web.req.qsparams:
+style = web.req.qsparams['style']
 
 diff = diffs(web, ctx, basectx, None, style)
 
@@ -449,7 +449,7 @@
 
 return dict(
 diff=diff,
-symrev=symrevorshortnode(req, ctx),
+symrev=symrevorshortnode(web.req, ctx),
 basenode=basectx.hex(),
 changesettag=showtags,
 changesetbookmark=showbookmarks,
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -108,7 +108,7 @@
 return manifest(web, req, None)
 
 try:
-fctx = webutil.filectx(web.repo, req)
+fctx = webutil.filectx(web.repo, web.req)
 except error.LookupError as inst:
 try:
 return manifest(web, req, None)
@@ -157,7 +157,7 @@
 file=f,
 path=webutil.up(f),
 text=lines(),
-symrev=webutil.symrevorshortnode(req, fctx),
+symrev=webutil.symrevorshortnode(web.req, fctx),
 rename=webutil.renamelink(fctx),
 permissions=fctx.manifest().flags(f),
 ishead=int(ishead),
@@ -190,7 +190,7 @@
 if not path:
 retur

D2796: hgweb: always return iterable from @webcommand functions (API)

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We had to hack up this function to support our transition to the
  new response API. Now that we're done with the transition (!!),
  we can return to returning an iterator of content chunks from
  these functions.
  
  It is tempting to return a normal object and not a generator.
  However, as the keyword extension demonstrates, extensions may
  wish to wrap commands and have a try..finally block around
  execution. Since there is a generator producing content and
  that generator could be executing code, the try..finally needs
  to live for as long as the generator is running. That means we
  have to return a generator so wrappers can consume the generator
  inside a try..finally.
  
  .. api::
  
hgweb @webcommand functions must use the new response object
passed in via ``web.res`` to initiate sending of a response.
The hgweb WSGI application will no longer start sending the
response automatically.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2796

AFFECTED FILES
  hgext/highlight/__init__.py
  hgext/keyword.py
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -57,12 +57,9 @@
 The functions should populate the ``rctx.res`` object with details
 about the HTTP response.
 
-The function can return the ``requestcontext.res`` instance to signal
-that it wants to use this object to generate the response. If an iterable
-is returned, the ``wsgirequest`` instance will be used and the returned
-content will constitute the response body. ``True`` can be returned to
-indicate that the function already sent output and the caller doesn't
-need to do anything more to send the response.
+The function returns a generator to be consumed by the WSGI application.
+For most commands, this should be the result from
+``web.res.sendresponse()``.
 
 Usage:
 
@@ -135,7 +132,7 @@
 .replace('\\', '').replace('"', '\\"'))
 web.res.headers['Content-Disposition'] = 'inline; filename="%s"' % filename
 web.res.setbodybytes(text)
-return web.res
+return web.res.sendresponse()
 
 def _filerevision(web, req, tmpl, fctx):
 f = fctx.path()
@@ -165,7 +162,7 @@
 ishead=int(ishead),
 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx
 
-return web.res
+return web.res.sendresponse()
 
 @webcommand('file')
 def file(web, req, tmpl):
@@ -355,7 +352,7 @@
 showforcekw=showforcekw,
 showunforcekw=showunforcekw))
 
-return web.res
+return web.res.sendresponse()
 
 @webcommand('changelog')
 def changelog(web, req, tmpl, shortlog=False):
@@ -455,7 +452,7 @@
 lessvars=lessvars,
 query=query))
 
-return web.res
+return web.res.sendresponse()
 
 @webcommand('shortlog')
 def shortlog(web, req, tmpl):
@@ -490,7 +487,7 @@
 ctx = webutil.changectx(web.repo, req)
 web.res.setbodygen(tmpl('changeset',
 **webutil.changesetentry(web, req, tmpl, ctx)))
-return web.res
+return web.res.sendresponse()
 
 rev = webcommand('rev')(changeset)
 
@@ -602,7 +599,7 @@
 archives=web.archivelist(hex(node)),
 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx
 
-return web.res
+return web.res.sendresponse()
 
 @webcommand('tags')
 def tags(web, req, tmpl):
@@ -638,7 +635,7 @@
 entriesnotip=lambda **x: entries(True, False, **x),
 latestentry=lambda **x: entries(True, True, **x)))
 
-return web.res
+return web.res.sendresponse()
 
 @webcommand('bookmarks')
 def bookmarks(web, req, tmpl):
@@ -679,7 +676,7 @@
 entries=lambda **x: entries(latestonly=False, **x),
 latestentry=lambda **x: entries(latestonly=True, **x)))
 
-return web.res
+return web.res.sendresponse()
 
 @webcommand('branches')
 def branches(web, req, tmpl):
@@ -704,7 +701,7 @@
 entries=entries,
 latestentry=latestentry))
 
-return web.res
+return web.res.sendresponse()
 
 @webcommand('summary')
 def summary(web, req, tmpl):
@@ -789,7 +786,7 @@
 archives=web.archivelist('tip'),
 labels=web.configlist('web', 'labels')))
 
-return web.res
+return web.res.sendresponse()
 
 @webcommand('filediff')
 def filediff(web, req, tmpl):
@@ -838,7 +835,7 @@
 diff=diffs,
 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx
 
-return web.res
+return web.res.sendresponse()
 
 diff = webcommand('diff')(filediff)
 
@@ -917,7 +914,7 @@
 comparison=comparison,
 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx
 
-return web.res
+return web.res.sendresponse()
 
 @webcommand('anno

D2805: hgweb: remove some use of wsgireq in hgwebdir

2018-03-10 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/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 read_allowed(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.read_allowed(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


D2793: hgweb: transition permissions hooks to modern request type (API)

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We're trying to remove ``wsgirequest``. The permissions hooks don't
  do anything they can't do with our new request type. So let's
  pass that in.
  
  This was the last use of ``wsgirequest`` in the wire protocol code!
  
  .. api::
  
hgweb.hgweb_mod.permhooks no longer take a ``wsgirequest`` instance
as an argument.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2793

AFFECTED FILES
  mercurial/hgweb/common.py
  mercurial/hgweb/hgweb_mod.py
  mercurial/wireprotoserver.py
  tests/test-http-bundle1.t
  tests/test-http.t
  tests/test-largefiles-wireproto.t

CHANGE DETAILS

diff --git a/tests/test-largefiles-wireproto.t 
b/tests/test-largefiles-wireproto.t
--- a/tests/test-largefiles-wireproto.t
+++ b/tests/test-largefiles-wireproto.t
@@ -424,7 +424,7 @@
   > import base64
   > from mercurial.hgweb import common
   > def perform_authentication(hgweb, req, op):
-  > auth = req.env.get('HTTP_AUTHORIZATION')
+  > auth = req.headers.get('Authorization')
   > if not auth:
   > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
   > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
diff --git a/tests/test-http.t b/tests/test-http.t
--- a/tests/test-http.t
+++ b/tests/test-http.t
@@ -168,7 +168,7 @@
   > import base64
   > from mercurial.hgweb import common
   > def perform_authentication(hgweb, req, op):
-  > auth = req.env.get('HTTP_AUTHORIZATION')
+  > auth = req.headers.get('Authorization')
   > if not auth:
   > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
   > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
@@ -510,7 +510,7 @@
   > from mercurial import util
   > from mercurial.hgweb import common
   > def perform_authentication(hgweb, req, op):
-  > cookie = req.env.get('HTTP_COOKIE')
+  > cookie = req.headers.get('Cookie')
   > if not cookie:
   > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'no-cookie')
   > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'Cookie: %s' % 
cookie)
diff --git a/tests/test-http-bundle1.t b/tests/test-http-bundle1.t
--- a/tests/test-http-bundle1.t
+++ b/tests/test-http-bundle1.t
@@ -177,7 +177,7 @@
   > import base64
   > from mercurial.hgweb import common
   > def perform_authentication(hgweb, req, op):
-  > auth = req.env.get('HTTP_AUTHORIZATION')
+  > auth = req.headers.get('Authorization')
   > if not auth:
   > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
   > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py
--- a/mercurial/wireprotoserver.py
+++ b/mercurial/wireprotoserver.py
@@ -148,13 +148,12 @@
 def iscmd(cmd):
 return cmd in wireproto.commands
 
-def handlewsgirequest(rctx, wsgireq, req, res, checkperm):
+def handlewsgirequest(rctx, req, res, checkperm):
 """Possibly process a wire protocol request.
 
 If the current request is a wire protocol request, the request is
 processed by this function.
 
-``wsgireq`` is a ``wsgirequest`` instance.
 ``req`` is a ``parsedrequest`` instance.
 ``res`` is a ``wsgiresponse`` instance.
 
@@ -197,7 +196,7 @@
 return True
 
 proto = httpv1protocolhandler(req, repo.ui,
-  lambda perm: checkperm(rctx, wsgireq, perm))
+  lambda perm: checkperm(rctx, req, 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
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
@@ -322,7 +322,7 @@
 res.headers['Content-Security-Policy'] = rctx.csp
 
 handled = wireprotoserver.handlewsgirequest(
-rctx, wsgireq, req, res, self.check_perm)
+rctx, req, res, self.check_perm)
 if handled:
 return res.sendresponse()
 
@@ -380,7 +380,7 @@
 
 # check read permissions non-static content
 if cmd != 'static':
-self.check_perm(rctx, wsgireq, None)
+self.check_perm(rctx, req, None)
 
 if cmd == '':
 req.qsparams['cmd'] = tmpl.cache['default']
diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -46,7 +46,7 @@
 authentication info). Return if op allowed, else raise an ErrorResponse
 exception.'''
 
-user = req.env.get(r'REMOTE_USER')
+user = req.remoteuser
 
 deny_read = hgweb.configlist('web', 'deny_read')
 if deny_read and (not user or ismember(hgweb.repo.ui, user, deny_read)):
@@ -62,14 +62,13 @@
 

D2803: hgweb: stop passing req and tmpl into @webcommand functions (API)

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We have effectively removed all consumers of the old wsgirequest
  type. The templater can be accessed on the requestcontext passed
  into the @webcommand function.
  
  For the most part, these arguments are unused. They only exist to
  provide backwards compatibility. And in the case of wsgirequest,
  use of that object could actively interfere with the new request
  object.
  
  So let's stop passing these objects to @webcommand functions.
  
  With this commit, wsgirequest is practically dead from the hgweb
  WSGI application. There are still some uses in hgwebdir though...
  
  .. api::
  
@webcommand functions now only receive a single argument. The
request and templater instances can be accessed via the
``req`` and ``templater`` attributes of the first argument.
Note that the request object is different from previous Mercurial
releases and consumers of the previous ``req`` 2nd argument
will need updating to use the new API.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2803

AFFECTED FILES
  hgext/highlight/__init__.py
  hgext/keyword.py
  hgext/largefiles/overrides.py
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/webcommands.py
  tests/hgweberror.py

CHANGE DETAILS

diff --git a/tests/hgweberror.py b/tests/hgweberror.py
--- a/tests/hgweberror.py
+++ b/tests/hgweberror.py
@@ -6,7 +6,7 @@
 webcommands,
 )
 
-def raiseerror(web, req, tmpl):
+def raiseerror(web):
 '''Dummy web command that raises an uncaught Exception.'''
 
 # Simulate an error after partial response.
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -65,7 +65,7 @@
 Usage:
 
 @webcommand('mycommand')
-def mycommand(web, req, tmpl):
+def mycommand(web):
 pass
 """
 
@@ -78,7 +78,7 @@
 return func
 
 @webcommand('log')
-def log(web, req, tmpl):
+def log(web):
 """
 /log[/{revision}[/{path}]]
 --
@@ -95,23 +95,23 @@
 """
 
 if web.req.qsparams.get('file'):
-return filelog(web, req, None)
+return filelog(web)
 else:
-return changelog(web, req, None)
+return changelog(web)
 
 @webcommand('rawfile')
-def rawfile(web, req, tmpl):
+def rawfile(web):
 guessmime = web.configbool('web', 'guessmime')
 
 path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', ''))
 if not path:
-return manifest(web, req, None)
+return manifest(web)
 
 try:
 fctx = webutil.filectx(web.repo, web.req)
 except error.LookupError as inst:
 try:
-return manifest(web, req, None)
+return manifest(web)
 except ErrorResponse:
 raise inst
 
@@ -135,7 +135,7 @@
 web.res.setbodybytes(text)
 return web.res.sendresponse()
 
-def _filerevision(web, req, fctx):
+def _filerevision(web, fctx):
 f = fctx.path()
 text = fctx.data()
 parity = paritygen(web.stripecount)
@@ -164,7 +164,7 @@
 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)))
 
 @webcommand('file')
-def file(web, req, tmpl):
+def file(web):
 """
 /file/{revision}[/{path}]
 -
@@ -184,16 +184,16 @@
 be rendered.
 """
 if web.req.qsparams.get('style') == 'raw':
-return rawfile(web, req, None)
+return rawfile(web)
 
 path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', ''))
 if not path:
-return manifest(web, req, None)
+return manifest(web)
 try:
-return _filerevision(web, req, webutil.filectx(web.repo, web.req))
+return _filerevision(web, webutil.filectx(web.repo, web.req))
 except error.LookupError as inst:
 try:
-return manifest(web, req, None)
+return manifest(web)
 except ErrorResponse:
 raise inst
 
@@ -354,7 +354,7 @@
 showunforcekw=showunforcekw)
 
 @webcommand('changelog')
-def changelog(web, req, tmpl, shortlog=False):
+def changelog(web, shortlog=False):
 """
 /changelog[/{revision}]
 ---
@@ -452,7 +452,7 @@
 query=query)
 
 @webcommand('shortlog')
-def shortlog(web, req, tmpl):
+def shortlog(web):
 """
 /shortlog
 -
@@ -463,10 +463,10 @@
 difference is the ``shortlog`` template will be rendered instead of the
 ``changelog`` template.
 """
-return changelog(web, req, None, shortlog=True)
+return changelog(web, shortlog=True)
 
 @webcommand('changeset')
-def changeset(web, req, tmpl):
+def changeset(web):
 """
 /changeset[/{revision}]
 ---
@@ -498,7 +498,7 @@
 return path
 
 @webcommand('manifest')
-def manifest(web, req, tmpl):
+def manifest(web):
 """
 /mani

D2795: hgweb: send errors using new response API

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Our slow march off of wsgirequest continues.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2795

AFFECTED FILES
  mercurial/hgweb/hgweb_mod.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -14,11 +14,10 @@
 from .common import (
 ErrorResponse,
 HTTP_BAD_REQUEST,
-HTTP_NOT_FOUND,
 HTTP_OK,
-HTTP_SERVER_ERROR,
 cspvalues,
 permhooks,
+statusmessage,
 )
 
 from .. import (
@@ -417,18 +416,25 @@
 return content
 
 except (error.LookupError, error.RepoLookupError) as err:
-wsgireq.respond(HTTP_NOT_FOUND, ctype)
 msg = pycompat.bytestr(err)
 if (util.safehasattr(err, 'name') and
 not isinstance(err,  error.ManifestLookupError)):
 msg = 'revision not found: %s' % err.name
-return tmpl('error', error=msg)
-except (error.RepoError, error.RevlogError) as inst:
-wsgireq.respond(HTTP_SERVER_ERROR, ctype)
-return tmpl('error', error=pycompat.bytestr(inst))
-except ErrorResponse as inst:
-wsgireq.respond(inst, ctype)
-return tmpl('error', error=pycompat.bytestr(inst))
+
+res.status = '404 Not Found'
+res.headers['Content-Type'] = ctype
+res.setbodygen(tmpl('error', error=msg))
+return res.sendresponse()
+except (error.RepoError, error.RevlogError) as e:
+res.status = '500 Internal Server Error'
+res.headers['Content-Type'] = ctype
+res.setbodygen(tmpl('error', error=pycompat.bytestr(e)))
+return res.sendresponse()
+except ErrorResponse as e:
+res.status = statusmessage(e.code, pycompat.bytestr(e))
+res.headers['Content-Type'] = ctype
+res.setbodygen(tmpl('error', error=pycompat.bytestr(e)))
+return res.sendresponse()
 
 def check_perm(self, rctx, req, op):
 for permhook in permhooks:



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


D2792: hgweb: port archive command to modern response API

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Well, I tried to go with PEP 's recommendations and only allow
  our WSGI application to emit data via a response generator.
  Unfortunately, the "archive" command calls into the zipfile and
  tarfile modules and these operator on file objects and must send
  their data to an object with write(). There's no easy way turn
  these write() calls into a generator.
  
  So, we teach our response type how to expose a file object like
  object that can be used to write() output. We try to keep the API
  consistent with how things work currently: callers must call a
  setbody*(), then sendresponse() to trigger sending of headers,
  and only then can they get a handle on the object to perform
  writing.
  
  This required overloading the return value of @webcommand functions
  even more. Fortunately, we're almost completely ported off the
  legacy API. So we should be able to simplify matters in the near
  future.
  
  A test relying on this functionality has also been updated to use
  the new API.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2792

AFFECTED FILES
  hgext/keyword.py
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/request.py
  mercurial/hgweb/webcommands.py
  tests/hgweberror.py

CHANGE DETAILS

diff --git a/tests/hgweberror.py b/tests/hgweberror.py
--- a/tests/hgweberror.py
+++ b/tests/hgweberror.py
@@ -10,9 +10,12 @@
 '''Dummy web command that raises an uncaught Exception.'''
 
 # Simulate an error after partial response.
-if 'partialresponse' in req.req.qsparams:
-req.respond(200, 'text/plain')
-req.write('partial content\n')
+if 'partialresponse' in web.req.qsparams:
+web.res.status = b'200 Script output follows'
+web.res.headers[b'Content-Type'] = b'text/plain'
+web.res.setbodywillwrite()
+list(web.res.sendresponse())
+web.res.getbodyfile().write(b'partial content\n')
 
 raise AttributeError('I am an uncaught error!')
 
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -19,14 +19,10 @@
 ErrorResponse,
 HTTP_FORBIDDEN,
 HTTP_NOT_FOUND,
-HTTP_OK,
 get_contact,
 paritygen,
 staticfile,
 )
-from . import (
-request as requestmod,
-)
 
 from .. import (
 archival,
@@ -64,7 +60,9 @@
 The function can return the ``requestcontext.res`` instance to signal
 that it wants to use this object to generate the response. If an iterable
 is returned, the ``wsgirequest`` instance will be used and the returned
-content will constitute the response body.
+content will constitute the response body. ``True`` can be returned to
+indicate that the function already sent output and the caller doesn't
+need to do anything more to send the response.
 
 Usage:
 
@@ -1210,21 +1208,24 @@
 'file(s) not found: %s' % file)
 
 mimetype, artype, extension, encoding = web.archivespecs[type_]
-headers = [
-('Content-Disposition', 'attachment; filename=%s%s' % (name, 
extension))
-]
+
+web.res.headers['Content-Type'] = mimetype
+web.res.headers['Content-Disposition'] = 'attachment; filename=%s%s' % (
+name, extension)
+
 if encoding:
-headers.append(('Content-Encoding', encoding))
-req.headers.extend(headers)
-req.respond(HTTP_OK, mimetype)
+web.res.headers['Content-Encoding'] = encoding
 
-bodyfh = requestmod.offsettrackingwriter(req.write)
+web.res.setbodywillwrite()
+assert list(web.res.sendresponse()) == []
+
+bodyfh = web.res.getbodyfile()
 
 archival.archive(web.repo, bodyfh, cnode, artype, prefix=name,
  matchfn=match,
  subrepos=web.configbool("web", "archivesubrepos"))
-return []
 
+return True
 
 @webcommand('static')
 def static(web, req, tmpl):
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -351,22 +351,42 @@
 
 self._bodybytes = None
 self._bodygen = None
+self._bodywillwrite = False
 self._started = False
+self._bodywritefn = None
+
+def _verifybody(self):
+if (self._bodybytes is not None or self._bodygen is not None
+or self._bodywillwrite):
+raise error.ProgrammingError('cannot define body multiple times')
 
 def setbodybytes(self, b):
 """Define the response body as static bytes."""
-if self._bodybytes is not None or self._bodygen is not None:
-raise error.ProgrammingError('cannot define body multiple times')
-
+self._verifybody()
 self._bodybytes = b
 self.headers['Content-Length'] = '%d' % len(b)
 
 def setbodygen(self, gen):

D2788: hgweb: remove one-off routing for file?style=raw

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Now that both functions are using the same API, we can unify how
  the command is called and perform command-specific behavior in the
  command itself instead of in the high-level router.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2788

AFFECTED FILES
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -186,6 +186,9 @@
 If ``path`` is not defined, information about the root directory will
 be rendered.
 """
+if web.req.qsparams.get('style') == 'raw':
+return rawfile(web, req, tmpl)
+
 path = webutil.cleanpath(web.repo, req.req.qsparams.get('file', ''))
 if not path:
 return manifest(web, req, tmpl)
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
@@ -399,12 +399,6 @@
 if cmd not in webcommands.__all__:
 msg = 'no such method: %s' % cmd
 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
-elif cmd == 'file' and req.qsparams.get('style') == 'raw':
-res.status = '200 Script output follows'
-res.headers['Content-Type'] = ctype
-content = webcommands.rawfile(rctx, wsgireq, tmpl)
-assert content is res
-return res.sendresponse()
 else:
 # Set some globals appropriate for web handlers. Commands can
 # override easily enough.



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


D2786: hgweb: support using new response object for web commands

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We have a "requestcontext" type for holding state for the current
  request. Why we pass in the wsgirequest and templater instance
  to @webcommand functions, I don't know.
  
  I like the idea of standardizing on using "requestcontext" for passing
  all state to @webcommand functions because that scales well without
  API changes every time you want to pass a new piece of data. So,
  we add our new request and response instances to "requestcontext" so
  @webcommand functions can access them.
  
  We also teach our command dispatcher to recognize a new calling
  convention. Instead of returning content from the @webcommand
  function, we return our response object. This signals that this
  response object is to be used for sending output. The keyword
  extension was wrapping various @webcommand and assuming the output
  was iterable, so we had to teach it about the new calling convention.
  
  To prove everything works, we convert the "filelog" @webcommand
  to use the new convention.
  
  The new calling convention is a bit wonky. I intend to improve this
  once all commands are ported to use the new response object.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2786

AFFECTED FILES
  hgext/keyword.py
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -53,6 +53,16 @@
 The decorator takes as its positional arguments the name/path the
 command should be accessible under.
 
+When called, functions receive as arguments a ``requestcontext``,
+``wsgirequest``, and a templater instance for generatoring output.
+The functions should populate the ``rctx.res`` object with details
+about the HTTP response.
+
+The function can return the ``requestcontext.res`` instance to signal
+that it wants to use this object to generate the response. If an iterable
+is returned, the ``wsgirequest`` instance will be used and the returned
+content will constitute the response body.
+
 Usage:
 
 @webcommand('mycommand')
@@ -1068,19 +1078,22 @@
 
 latestentry = entries[:1]
 
-return tmpl("filelog",
-file=f,
-nav=nav,
-symrev=webutil.symrevorshortnode(req, fctx),
-entries=entries,
-descend=descend,
-patch=patch,
-latestentry=latestentry,
-linerange=linerange,
-revcount=revcount,
-morevars=morevars,
-lessvars=lessvars,
-**pycompat.strkwargs(webutil.commonentry(web.repo, fctx)))
+web.res.setbodygen(tmpl(
+'filelog',
+file=f,
+nav=nav,
+symrev=webutil.symrevorshortnode(req, fctx),
+entries=entries,
+descend=descend,
+patch=patch,
+latestentry=latestentry,
+linerange=linerange,
+revcount=revcount,
+morevars=morevars,
+lessvars=lessvars,
+**pycompat.strkwargs(webutil.commonentry(web.repo, fctx
+
+return web.res
 
 @webcommand('archive')
 def archive(web, req, tmpl):
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
@@ -91,9 +91,11 @@
 is prone to race conditions. Instances of this class exist to hold
 mutable and race-free state for requests.
 """
-def __init__(self, app, repo):
+def __init__(self, app, repo, req, res):
 self.repo = repo
 self.reponame = app.reponame
+self.req = req
+self.res = res
 
 self.archivespecs = archivespecs
 
@@ -305,7 +307,7 @@
 def _runwsgi(self, wsgireq, repo):
 req = wsgireq.req
 res = wsgireq.res
-rctx = requestcontext(self, repo)
+rctx = requestcontext(self, repo, req, res)
 
 # This state is global across all threads.
 encoding.encoding = rctx.config('web', 'encoding')
@@ -401,7 +403,15 @@
 rctx.ctype = ctype
 content = webcommands.rawfile(rctx, wsgireq, tmpl)
 else:
+# Set some globals appropriate for web handlers. Commands can
+# override easily enough.
+res.status = '200 Script output follows'
+res.headers['Content-Type'] = ctype
 content = getattr(webcommands, cmd)(rctx, wsgireq, tmpl)
+
+if content is res:
+return res.sendresponse()
+
 wsgireq.respond(HTTP_OK, ctype)
 
 return content
diff --git a/hgext/keyword.py b/hgext/keyword.py
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -621,7 +621,10 @@
 origma

D2794: hgweb: refactor 304 handling code

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We had generic code in wsgirequest for handling HTTP 304 responses.
  We also had a special case for it in the catch all exception handler
  in the WSGI application.
  
  We only ever raise 304 in one place. So, we don't need to treat it
  specially in the catch all exception handler.
  
  But it is useful to validate behavior of 304 responses. We port the
  code that sends a 304 to use the new response API. We then move the
  code for screening 304 sanity into the new response API.
  
  As part of doing so, we discovered that we would send
  Content-Length: 0. This is not allowed. So, we fix our response code
  to not emit that header for empty response bodies.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2794

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
@@ -15,7 +15,6 @@
 
 from .common import (
 ErrorResponse,
-HTTP_NOT_MODIFIED,
 statusmessage,
 )
 
@@ -361,7 +360,10 @@
 raise error.ProgrammingError('cannot define body multiple times')
 
 def setbodybytes(self, b):
-"""Define the response body as static bytes."""
+"""Define the response body as static bytes.
+
+The empty string signals that there is no response body.
+"""
 self._verifybody()
 self._bodybytes = b
 self.headers['Content-Length'] = '%d' % len(b)
@@ -408,6 +410,35 @@
 and not self._bodywillwrite):
 raise error.ProgrammingError('response body not defined')
 
+# RFC 7232 Section 4.1 states that a 304 MUST generate one of
+# {Cache-Control, Content-Location, Date, ETag, Expires, Vary}
+# and SHOULD NOT generate other headers unless they could be used
+# to guide cache updates. Furthermore, RFC 7230 Section 3.3.2
+# states that no response body can be issued. Content-Length can
+# be sent. But if it is present, it should be the size of the response
+# that wasn't transferred.
+if self.status.startswith('304 '):
+# setbodybytes('') will set C-L to 0. This doesn't conform with the
+# spec. So remove it.
+if self.headers.get('Content-Length') == '0':
+del self.headers['Content-Length']
+
+# Strictly speaking, this is too strict. But until it causes
+# problems, let's be strict.
+badheaders = {k for k in self.headers.keys()
+  if k.lower() not in ('date', 'etag', 'expires',
+   'cache-control',
+   'content-location',
+   'vary')}
+if badheaders:
+raise error.ProgrammingError(
+'illegal header on 304 response: %s' %
+', '.join(sorted(badheaders)))
+
+if self._bodygen is not None or self._bodywillwrite:
+raise error.ProgrammingError("must use setbodybytes('') with "
+ "304 responses")
+
 # 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
@@ -539,13 +570,6 @@
 
 if isinstance(status, ErrorResponse):
 self.headers.extend(status.headers)
-if status.code == HTTP_NOT_MODIFIED:
-# RFC 2616 Section 10.3.5: 304 Not Modified has cases where
-# it MUST NOT include any headers other than these and no
-# body
-self.headers = [(k, v) for (k, v) in self.headers if
-k in ('Date', 'ETag', 'Expires',
-  'Cache-Control', 'Vary')]
 status = statusmessage(status.code, pycompat.bytestr(status))
 elif status == 200:
 status = '200 Script output follows'
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
@@ -15,7 +15,6 @@
 ErrorResponse,
 HTTP_BAD_REQUEST,
 HTTP_NOT_FOUND,
-HTTP_NOT_MODIFIED,
 HTTP_OK,
 HTTP_SERVER_ERROR,
 cspvalues,
@@ -391,7 +390,10 @@
 if rctx.configbool('web', 'cache') and not rctx.nonce:
 tag = 'W/"%d"' % self.mtime
 if req.headers.get('If-None-Match') == tag:
-raise ErrorResponse(HTTP_NOT_MODIFIED)
+res.status = '304 Not Modified'
+   

D2787: hgweb: port most @webcommand to use modern response type

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This only focused on porting the return value.
  
  raw file requests are wonky because they go through a separate code
  path at the dispatch layer. Now that everyone is using the same
  API, we could clean this up.
  
  It's worth noting that wsgirequest.respond() allows sending the
  Content-Disposition header, but the only user of that feature was
  removed as part of this change (with the setting of the header
  now being performed inline).
  
  A few @webcommand are not as straightforward as the others and
  they have not been ported yet.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2787

AFFECTED FILES
  hgext/highlight/__init__.py
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -106,17 +106,13 @@
 
 path = webutil.cleanpath(web.repo, req.req.qsparams.get('file', ''))
 if not path:
-content = manifest(web, req, tmpl)
-req.respond(HTTP_OK, web.ctype)
-return content
+return manifest(web, req, tmpl)
 
 try:
 fctx = webutil.filectx(web.repo, req)
 except error.LookupError as inst:
 try:
-content = manifest(web, req, tmpl)
-req.respond(HTTP_OK, web.ctype)
-return content
+return manifest(web, req, tmpl)
 except ErrorResponse:
 raise inst
 
@@ -133,8 +129,12 @@
 if mt.startswith('text/'):
 mt += '; charset="%s"' % encoding.encoding
 
-req.respond(HTTP_OK, mt, path, body=text)
-return []
+web.res.headers['Content-Type'] = mt
+filename = (path.rpartition('/')[-1]
+.replace('\\', '').replace('"', '\\"'))
+web.res.headers['Content-Disposition'] = 'inline; filename="%s"' % filename
+web.res.setbodybytes(text)
+return web.res
 
 def _filerevision(web, req, tmpl, fctx):
 f = fctx.path()
@@ -153,15 +153,18 @@
"linenumber": "% 6d" % (lineno + 1),
"parity": next(parity)}
 
-return tmpl("filerevision",
-file=f,
-path=webutil.up(f),
-text=lines(),
-symrev=webutil.symrevorshortnode(req, fctx),
-rename=webutil.renamelink(fctx),
-permissions=fctx.manifest().flags(f),
-ishead=int(ishead),
-**pycompat.strkwargs(webutil.commonentry(web.repo, fctx)))
+web.res.setbodygen(tmpl(
+'filerevision',
+file=f,
+path=webutil.up(f),
+text=lines(),
+symrev=webutil.symrevorshortnode(req, fctx),
+rename=webutil.renamelink(fctx),
+permissions=fctx.manifest().flags(f),
+ishead=int(ishead),
+**pycompat.strkwargs(webutil.commonentry(web.repo, fctx
+
+return web.res
 
 @webcommand('file')
 def file(web, req, tmpl):
@@ -335,11 +338,20 @@
 tip = web.repo['tip']
 parity = paritygen(web.stripecount)
 
-return tmpl('search', query=query, node=tip.hex(), symrev='tip',
-entries=changelist, archives=web.archivelist("tip"),
-morevars=morevars, lessvars=lessvars,
-modedesc=searchfunc[1],
-showforcekw=showforcekw, showunforcekw=showunforcekw)
+web.res.setbodygen(tmpl(
+'search',
+query=query,
+node=tip.hex(),
+symrev='tip',
+entries=changelist,
+archives=web.archivelist('tip'),
+morevars=morevars,
+lessvars=lessvars,
+modedesc=searchfunc[1],
+showforcekw=showforcekw,
+showunforcekw=showunforcekw))
+
+return web.res
 
 @webcommand('changelog')
 def changelog(web, req, tmpl, shortlog=False):
@@ -423,12 +435,23 @@
 else:
 nextentry = []
 
-return tmpl('shortlog' if shortlog else 'changelog', changenav=changenav,
-node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
-entries=entries,
-latestentry=latestentry, nextentry=nextentry,
-archives=web.archivelist("tip"), revcount=revcount,
-morevars=morevars, lessvars=lessvars, query=query)
+web.res.setbodygen(tmpl(
+'shortlog' if shortlog else 'changelog',
+changenav=changenav,
+node=ctx.hex(),
+rev=pos,
+symrev=symrev,
+changesets=count,
+entries=entries,
+latestentry=latestentry,
+nextentry=nextentry,
+archives=web.archivelist('tip'),
+revcount=revcount,
+morevars=morevars,
+lessvars=lessvars,
+query=query))
+
+return web.res
 
 @webcommand('shortlog')
 def shortlog(web, req, tmpl):
@@ -461,8 +484,9 @@
 templates related to diffs

D2790: tests: additional test coverage of archive web command

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This command is special in a few ways. First, it is the only command
  using the write() function from WSGI's start_response() function.
  Second, it is setting a custom content-disposition header.
  
  We change the test so it prints out full details of the HTTP
  response. We also save the response body to a file so we can
  verify its size and hash. The hash check will help ensure that
  archive generation is deterministic.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2790

AFFECTED FILES
  tests/test-archive.t

CHANGE DETAILS

diff --git a/tests/test-archive.t b/tests/test-archive.t
--- a/tests/test-archive.t
+++ b/tests/test-archive.t
@@ -106,10 +106,13 @@
   > --config extensions.blackbox= --config blackbox.track=develwarn
   > cat hg.pid >> $DAEMON_PIDS
   > echo % $1 allowed should give 200
-  > get-with-headers.py localhost:$HGPORT "archive/tip.$2" | head -n 1
+  > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$2" 
-
+  > f --size --sha1 body
   > echo % $3 and $4 disallowed should both give 403
-  > get-with-headers.py localhost:$HGPORT "archive/tip.$3" | head -n 1
-  > get-with-headers.py localhost:$HGPORT "archive/tip.$4" | head -n 1
+  > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$3" 
-
+  > f --size --sha1 body
+  > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$4" 
-
+  > f --size --sha1 body
   > killdaemons.py
   > cat errors.log
   > hg blackbox --config extensions.blackbox= --config blackbox.track=
@@ -121,42 +124,174 @@
   $ test_archtype gz tar.gz tar.bz2 zip
   % gz allowed should give 200
   200 Script output follows
+  content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
+  content-type: application/x-gzip
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505
   % tar.bz2 and zip disallowed should both give 403
   403 Archive type not allowed: bz2
+  content-type: text/html; charset=ascii
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
   403 Archive type not allowed: zip
+  content-type: text/html; charset=ascii
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
   $ test_archtype bz2 tar.bz2 zip tar.gz
   % bz2 allowed should give 200
   200 Script output follows
+  content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.bz2
+  content-type: application/x-bzip2
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=426, sha1=8d87f5aba6e14f1bfea6c232985982c278b2fb0b
   % zip and tar.gz disallowed should both give 403
   403 Archive type not allowed: zip
+  content-type: text/html; charset=ascii
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
   403 Archive type not allowed: gz
+  content-type: text/html; charset=ascii
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
   $ test_archtype zip zip tar.gz tar.bz2
   % zip allowed should give 200
   200 Script output follows
+  content-disposition: attachment; filename=test-archive-1701ef1f1510.zip
+  content-type: application/zip
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=1377, sha1=677b14d3d048778d5eb5552c14a67e6192068650
   % tar.gz and tar.bz2 disallowed should both give 403
   403 Archive type not allowed: gz
+  content-type: text/html; charset=ascii
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
   403 Archive type not allowed: bz2
+  content-type: text/html; charset=ascii
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
 
 check http return codes (with deprecated option)
 
   $ test_archtype_deprecated gz tar.gz tar.bz2 zip
   % gz allowed should give 200
   200 Script output follows
+  content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
+  content-type: application/x-gzip
+  date: * (glob)
+  etag: W/"*" (glob)
+  server: * (glob)
+  transfer-encoding: chunked
+  
+  body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505
   % tar.bz2 and zip disallowed should both give 403
   403 Archive type 

D2789: hgweb: port static file handling to new response API

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  hgwebdir_mod hasn't received as much porting effort. So we had to
  do some minor plumbing to get it to match hgweb_mod and to support
  the new response object.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2789

AFFECTED FILES
  mercurial/hgweb/common.py
  mercurial/hgweb/hgwebdir_mod.py
  mercurial/hgweb/webcommands.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -1232,8 +1232,9 @@
 if isinstance(tp, str):
 tp = [tp]
 static = [os.path.join(p, 'static') for p in tp]
-staticfile(static, fname, req)
-return []
+
+staticfile(static, fname, web.res)
+return web.res
 
 @webcommand('graph')
 def graph(web, req, tmpl):
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
@@ -230,19 +230,25 @@
 
 def _runwsgi(self, wsgireq):
 req = wsgireq.req
+res = wsgireq.res
 
 try:
 self.refresh()
 
 csp, nonce = cspvalues(self.ui)
 if csp:
+res.headers['Content-Security-Policy'] = csp
 wsgireq.headers.append(('Content-Security-Policy', csp))
 
 virtual = wsgireq.env.get("PATH_INFO", "").strip('/')
 tmpl = self.templater(wsgireq, nonce)
 ctype = tmpl('mimetype', encoding=encoding.encoding)
 ctype = templater.stringify(ctype)
 
+# Global defaults. These can be overridden by any handler.
+res.status = '200 Script output follows'
+res.headers['Content-Type'] = ctype
+
 # a static file
 if virtual.startswith('static/') or 'static' in req.qsparams:
 if virtual.startswith('static/'):
@@ -256,8 +262,9 @@
 if isinstance(tp, str):
 tp = [tp]
 static = [os.path.join(p, 'static') for p in tp]
-staticfile(static, fname, wsgireq)
-return []
+
+staticfile(static, fname, res)
+return res.sendresponse()
 
 # top-level index
 
diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -153,7 +153,7 @@
 
 return True
 
-def staticfile(directory, fname, req):
+def staticfile(directory, fname, res):
 """return a file inside directory with guessed Content-Type header
 
 fname always uses '/' as directory separator and isn't allowed to
@@ -178,7 +178,9 @@
 with open(path, 'rb') as fh:
 data = fh.read()
 
-req.respond(HTTP_OK, ct, body=data)
+res.headers['Content-Type'] = ct
+res.setbodybytes(data)
+return res
 except TypeError:
 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
 except OSError as err:



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


D2785: hgweb: inline caching() and port to modern mechanisms

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We only had one consumer of this simple function. While it could be
  a generic function, let's not over abstract the code.
  
  As part of inlining, we port it off wsgirequest, fix some Python 3
  issues, and set a response header on our new response object so it
  is ready once we start using it to send responses.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2785

AFFECTED FILES
  mercurial/hgweb/common.py
  mercurial/hgweb/hgweb_mod.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -18,7 +18,6 @@
 HTTP_NOT_MODIFIED,
 HTTP_OK,
 HTTP_SERVER_ERROR,
-caching,
 cspvalues,
 permhooks,
 )
@@ -388,7 +387,13 @@
 # Don't enable caching if using a CSP nonce because then it 
wouldn't
 # be a nonce.
 if rctx.configbool('web', 'cache') and not rctx.nonce:
-caching(self, wsgireq) # sets ETag header or raises 
NOT_MODIFIED
+tag = 'W/"%d"' % self.mtime
+if req.headers.get('If-None-Match') == tag:
+raise ErrorResponse(HTTP_NOT_MODIFIED)
+
+wsgireq.headers.append((r'ETag', pycompat.sysstr(tag)))
+res.headers['ETag'] = tag
+
 if cmd not in webcommands.__all__:
 msg = 'no such method: %s' % cmd
 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -214,12 +214,6 @@
 config("ui", "username") or
 encoding.environ.get("EMAIL") or "")
 
-def caching(web, req):
-tag = r'W/"%d"' % web.mtime
-if req.env.get('HTTP_IF_NONE_MATCH') == tag:
-raise ErrorResponse(HTTP_NOT_MODIFIED)
-req.headers.append(('ETag', tag))
-
 def cspvalues(ui):
 """Obtain the Content-Security-Policy header and nonce value.
 



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


D2783: hgweb: expose URL scheme and REMOTE_* attributes

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  These are consulted by the HTTP wire protocol handler by reading from
  the env dict. Let's expose them as attributes instead.
  
  With the wire protocol handler updates to use the new attributes, we
  no longer have any consumers of the legacy wsgirequest type in the
  wire protocol code (outside of a proxied call to the permissions
  checker). So, we remove most references to it.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2783

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
@@ -53,8 +53,7 @@
 return ''.join(chunks)
 
 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
-def __init__(self, wsgireq, req, ui, checkperm):
-self._wsgireq = wsgireq
+def __init__(self, req, ui, checkperm):
 self._req = req
 self._ui = ui
 self._checkperm = checkperm
@@ -117,9 +116,9 @@
 
 def client(self):
 return 'remote:%s:%s:%s' % (
-self._wsgireq.env.get('wsgi.url_scheme') or 'http',
-urlreq.quote(self._wsgireq.env.get('REMOTE_HOST', '')),
-urlreq.quote(self._wsgireq.env.get('REMOTE_USER', '')))
+self._req.urlscheme,
+urlreq.quote(self._req.remotehost or ''),
+urlreq.quote(self._req.remoteuser or ''))
 
 def addcapabilities(self, repo, caps):
 caps.append('httpheader=%d' %
@@ -197,15 +196,15 @@
 res.setbodybytes('0\n%s\n' % b'Not Found')
 return True
 
-proto = httpv1protocolhandler(wsgireq, req, repo.ui,
+proto = httpv1protocolhandler(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:
-_callhttp(repo, wsgireq, req, res, proto, cmd)
+_callhttp(repo, req, res, proto, cmd)
 except hgwebcommon.ErrorResponse as e:
 for k, v in e.headers:
 res.headers[k] = v
@@ -256,7 +255,7 @@
 opts = {'level': ui.configint('server', 'zliblevel')}
 return HGTYPE, util.compengines['zlib'], opts
 
-def _callhttp(repo, wsgireq, req, res, proto, cmd):
+def _callhttp(repo, req, res, proto, cmd):
 # Avoid cycle involving hg module.
 from .hgweb import common as hgwebcommon
 
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -129,6 +129,12 @@
 # of HTTP: Host header for hostname. This is likely what clients used.
 advertisedurl = attr.ib()
 advertisedbaseurl = attr.ib()
+# URL scheme (part before ``://``). e.g. ``http`` or ``https``.
+urlscheme = attr.ib()
+# Value of REMOTE_USER, if set, or None.
+remoteuser = attr.ib()
+# Value of REMOTE_HOST, if set, or None.
+remotehost = attr.ib()
 # WSGI application path.
 apppath = attr.ib()
 # List of path parts to be used for dispatch.
@@ -270,6 +276,9 @@
  url=fullurl, baseurl=baseurl,
  advertisedurl=advertisedfullurl,
  advertisedbaseurl=advertisedbaseurl,
+ urlscheme=env['wsgi.url_scheme'],
+ remoteuser=env.get('REMOTE_USER'),
+ remotehost=env.get('REMOTE_HOST'),
  apppath=apppath,
  dispatchparts=dispatchparts, 
dispatchpath=dispatchpath,
  havepathinfo='PATH_INFO' in env,



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


D2784: hgweb: expose repo name on parsedrequest

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  I'm not a fan of doing this because I want to find a better solution to
  the REPO_NAME hack. But this change gets us a few steps closer to
  eliminating use of wsgirequest. We can worry about fixing REPO_NAME
  once wsgirequest is gone.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2784

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
@@ -144,6 +144,8 @@
 # Whether there is a path component to this request. This can be true
 # when ``dispatchpath`` is empty due to REPO_NAME muckery.
 havepathinfo = attr.ib()
+# The name of the repository being accessed.
+reponame = attr.ib()
 # Raw query string (part after "?" in URL).
 querystring = attr.ib()
 # multidict of query string parameters.
@@ -282,6 +284,7 @@
  apppath=apppath,
  dispatchparts=dispatchparts, 
dispatchpath=dispatchpath,
  havepathinfo='PATH_INFO' in env,
+ reponame=env.get('REPO_NAME'),
  querystring=querystring,
  qsparams=qsparams,
  headers=headers,
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
@@ -141,7 +141,7 @@
 if typ in allowed or self.configbool('web', 'allow%s' % typ):
 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
 
-def templater(self, wsgireq, req):
+def templater(self, req):
 # determine scheme, port and server name
 # this is needed to create absolute urls
 logourl = self.config('web', 'logourl')
@@ -159,17 +159,18 @@
 # figure out which style to use
 
 vars = {}
-styles, (style, mapfile) = getstyle(wsgireq.req, self.config,
+styles, (style, mapfile) = getstyle(req, self.config,
 self.templatepath)
 if style == styles[0]:
 vars['style'] = style
 
 sessionvars = webutil.sessionvars(vars, '?')
 
 if not self.reponame:
 self.reponame = (self.config('web', 'name', '')
- or wsgireq.env.get('REPO_NAME')
- or req.apppath or self.repo.root)
+ or req.reponame
+ or req.apppath
+ or self.repo.root)
 
 def websubfilter(text):
 return templatefilters.websub(text, self.websubtable)
@@ -372,7 +373,7 @@
 # process the web interface request
 
 try:
-tmpl = rctx.templater(wsgireq, req)
+tmpl = rctx.templater(req)
 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


[PATCH] wireproto: explicitly flush stdio to prevent stalls on Windows

2018-03-10 Thread Matt Harbison
# HG changeset patch
# User Matt Harbison 
# Date 1520744281 18000
#  Sat Mar 10 23:58:01 2018 -0500
# Node ID 1145671119a40e82932f63f83ad4049727416174
# Parent  963b4223d14fa419df2a82fbe47cd55075707b6a
wireproto: explicitly flush stdio to prevent stalls on Windows

This is the key to fixing the hangs on Windows in D2720[1].  I put flushes in a
bunch of other places that didn't help, but I suspect that's more a lack of test
coverage than anything else.

Chasing down stuff like this is pretty painful.  I'm wondering if we can put a
proxy around sys.stderr (and sys.stdout?) on Windows (only when daemonized?)
that will flush on every write (or at least every write with a '\n').

[1] 
https://www.mercurial-scm.org/pipermail/mercurial-devel/2018-March/113352.html

diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -2769,6 +2769,8 @@ def debugwireproto(ui, repo, **opts):
 
 # Now perform actions based on the parsed wire language instructions.
 for action, lines in blocks:
+util.stdout.flush()
+
 if action in ('raw', 'raw+'):
 # Concatenate the data together.
 data = ''.join(l.lstrip() for l in lines)
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -1069,6 +1069,7 @@ def unbundle(repo, proto, heads):
 util.stderr.write("abort: %s\n" % exc)
 if exc.hint is not None:
 util.stderr.write("(%s)\n" % exc.hint)
+util.stderr.flush()
 return pushres(0, output.getvalue() if output else '')
 except error.PushRaced:
 return pusherr(pycompat.bytestr(exc),
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH] hook: ensure stderr is flushed when an exception is raised, for test stability

2018-03-10 Thread Matt Harbison
# HG changeset patch
# User Matt Harbison 
# Date 1520737378 18000
#  Sat Mar 10 22:02:58 2018 -0500
# Node ID 5dd79bb7e5a3cf33d04c00e7c6c52070f472149d
# Parent  7c4e5abd9e7e903e6916b30ccc0d2b26d69a5fca
hook: ensure stderr is flushed when an exception is raised, for test stability

Windows has had issues with output order in test-ssh-proto-unbundle.t[1] since
it was created a few weeks ago.  Each of the problems occurred when an exception
was thrown out of the hook.

Now the only thing blocking D2720 is the fact that the "abort: ..." lines on
stderr are totally AWOL.  I have no idea where there are.

[1] 
https://buildbot.mercurial-scm.org/builders/Win7%20x86_64%20hg%20tests/builds/541/steps/run-tests.py%20%28python%202.7.13%29/logs/stdio

diff --git a/mercurial/hook.py b/mercurial/hook.py
--- a/mercurial/hook.py
+++ b/mercurial/hook.py
@@ -265,12 +265,12 @@ def runhooks(ui, repo, htype, hooks, thr
 raised = False
 
 res[hname] = r, raised
+finally:
+# The stderr is fully buffered on Windows when connected to a pipe.
+# A forcible flush is required to make small stderr data in the
+# remote side available to the client immediately.
+util.stderr.flush()
 
-# The stderr is fully buffered on Windows when connected to a pipe.
-# A forcible flush is required to make small stderr data in the
-# remote side available to the client immediately.
-util.stderr.flush()
-finally:
 if _redirect and oldstdout >= 0:
 util.stdout.flush()  # write hook output to stderr fd
 os.dup2(oldstdout, stdoutno)
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2782: hgweb: remove wsgirequest.form (API)

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Now that everything is ported to consume from parsedrequest.qsparams,
  we no longer have a need for wsgirequest.form. Let's remove all
  references to it.
  
  .. api::
  
The WSGI request object no longer exposes a ``form`` attribute
containing parsed query string data. Use the ``qsparams`` attribute
instead.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2782

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
@@ -426,7 +426,6 @@
 self.run_once = wsgienv[r'wsgi.run_once']
 self.env = wsgienv
 self.req = parserequestfromenv(wsgienv, inp)
-self.form = self.req.qsparams.asdictoflists()
 self.res = wsgiresponse(self.req, start_response)
 self._start_response = start_response
 self.server_write = None
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
@@ -341,19 +341,15 @@
 
 # avoid accepting e.g. style parameter as command
 if util.safehasattr(webcommands, cmd):
-wsgireq.form['cmd'] = [cmd]
 req.qsparams['cmd'] = cmd
 
 if cmd == 'static':
-wsgireq.form['file'] = ['/'.join(args)]
 req.qsparams['file'] = '/'.join(args)
 else:
 if args and args[0]:
 node = args.pop(0).replace('%2F', '/')
-wsgireq.form['node'] = [node]
 req.qsparams['node'] = node
 if args:
-wsgireq.form['file'] = args
 if 'file' in req.qsparams:
 del req.qsparams['file']
 for a in args:
@@ -368,9 +364,7 @@
 for type_, spec in rctx.archivespecs.iteritems():
 ext = spec[2]
 if fn.endswith(ext):
-wsgireq.form['node'] = [fn[:-len(ext)]]
 req.qsparams['node'] = fn[:-len(ext)]
-wsgireq.form['type'] = [type_]
 req.qsparams['type'] = type_
 else:
 cmd = req.qsparams.get('cmd', '')
@@ -387,7 +381,6 @@
 self.check_perm(rctx, wsgireq, None)
 
 if cmd == '':
-wsgireq.form['cmd'] = [tmpl.cache['default']]
 req.qsparams['cmd'] = tmpl.cache['default']
 cmd = req.qsparams['cmd']
 



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-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6840.
indygreg edited the summary of this revision.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2776?vs=6838&id=6840

REVISION DETAIL
  https://phab.mercurial-scm.org/D2776

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
@@ -79,7 +79,7 @@
 return [data[k] for k in keys]
 
 def _args(self):
-args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy())
+args = self._req.qsparams.asdictoflists()
 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
 if postlen:
 args.update(urlreq.parseqs(
@@ -170,10 +170,10 @@
 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
 # string parameter. If it isn't present, this isn't a wire protocol
 # request.
-if 'cmd' not in req.querystringdict:
+if 'cmd' not in req.qsparams:
 return False
 
-cmd = req.querystringdict['cmd'][0]
+cmd = req.qsparams['cmd']
 
 # The "cmd" request parameter is used by both the wire protocol and hgweb.
 # While not all wire protocol commands are available for all transports,
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -28,6 +28,90 @@
 util,
 )
 
+class multidict(object):
+"""A dict like object that can store multiple values for a key.
+
+Used to store parsed request parameters.
+
+This is inspired by WebOb's class of the same name.
+"""
+def __init__(self):
+# Stores (key, value) 2-tuples. This isn't the most efficient. But we
+# don't rely on parameters that much, so it shouldn't be a perf issue.
+# we can always add dict for fast lookups.
+self._items = []
+
+def __getitem__(self, key):
+"""Returns the last set value for a key."""
+for k, v in reversed(self._items):
+if k == key:
+return v
+
+raise KeyError(key)
+
+def __setitem__(self, key, value):
+"""Replace a values for a key with a new value."""
+try:
+del self[key]
+except KeyError:
+pass
+
+self._items.append((key, value))
+
+def __delitem__(self, key):
+"""Delete all values for a key."""
+oldlen = len(self._items)
+
+self._items[:] = [(k, v) for k, v in self._items if k != key]
+
+if oldlen == len(self._items):
+raise KeyError(key)
+
+def __contains__(self, key):
+return any(k == key for k, v in self._items)
+
+def __len__(self):
+return len(self._items)
+
+def get(self, key, default=None):
+try:
+return self.__getitem__(key)
+except KeyError:
+return default
+
+def add(self, key, value):
+"""Add a new value for a key. Does not replace existing values."""
+self._items.append((key, value))
+
+def getall(self, key):
+"""Obtains all values for a key."""
+return [v for k, v in self._items if k == key]
+
+def getone(self, key):
+"""Obtain a single value for a key.
+
+Raises KeyError if key not defined or it has multiple values set.
+"""
+vals = self.getall(key)
+
+if not vals:
+raise KeyError(key)
+
+if len(vals) > 1:
+raise KeyError('multiple values for %r' % key)
+
+return vals[0]
+
+def asdictoflists(self):
+d = {}
+for k, v in self._items:
+if k in d:
+d[k].append(v)
+else:
+d[k] = [v]
+
+return d
+
 @attr.s(frozen=True)
 class parsedrequest(object):
 """Represents a parsed WSGI request.
@@ -56,10 +140,8 @@
 havepathinfo = attr.ib()
 # Raw query string (part after "?" in URL).
 querystring = attr.ib()
-# List of 2-tuples of query string arguments.
-querystringlist = attr.ib()
-# Dict of query string arguments. Values are lists with at least 1 item.
-querystringdict = attr.ib()
+# multidict of query string parameters.
+qsparams = attr.ib()
 # wsgiref.headers.Headers instance. Operates like a dict with case
 # insensitive keys.
 headers = attr.ib()
@@ -157,14 +239,9 @@
 
 # We store as a list so we have ordering information. We also store as
 # a dict to facilitate fast lookup.
-querystringlist = util.urlreq.parseqsl(querystring, keep_blank_values=True)
-
-querystringdict = {}
-for k, v in querystringlist:
-if k in querystringdict:
-querystringdict[k].append(v)
-else:
-querystringdict[k] = [v]
+qsparams = multidict()
+for k, v in util.urlreq.parseqsl(querystring, keep_blank_values=True):
+qspara

D2781: hgweb: perform all parameter lookup via qsparams

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  I think I managed to update all call sites using wsgirequest.form
  to use parsedrequest.qsparams.
  
  Since behavior of qsparams is to retrieve last value, behavior will
  change if a parameter was specified multiple times. But I think this
  is acceptable.
  
  I'm not a fan of the `req.req.qsparams` pattern. And some of the
  modified code could be written better. But I was aiming for a
  straight port with this change. Cleanup can come later.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2781

AFFECTED FILES
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/hgwebdir_mod.py
  mercurial/hgweb/webcommands.py
  mercurial/hgweb/webutil.py
  tests/hgweberror.py

CHANGE DETAILS

diff --git a/tests/hgweberror.py b/tests/hgweberror.py
--- a/tests/hgweberror.py
+++ b/tests/hgweberror.py
@@ -10,7 +10,7 @@
 '''Dummy web command that raises an uncaught Exception.'''
 
 # Simulate an error after partial response.
-if 'partialresponse' in req.form:
+if 'partialresponse' in req.req.qsparams:
 req.respond(200, 'text/plain')
 req.write('partial content\n')
 
diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -177,7 +177,7 @@
  section=section, whitespace=True)
 
 for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
-v = req.form.get(k, [None])[0]
+v = req.req.qsparams.get(k)
 if v is not None:
 v = util.parsebool(v)
 setattr(diffopts, k, v if v is not None else True)
@@ -295,34 +295,34 @@
 
 def changectx(repo, req):
 changeid = "tip"
-if 'node' in req.form:
-changeid = req.form['node'][0]
+if 'node' in req.req.qsparams:
+changeid = req.req.qsparams['node']
 ipos = changeid.find(':')
 if ipos != -1:
 changeid = changeid[(ipos + 1):]
-elif 'manifest' in req.form:
-changeid = req.form['manifest'][0]
+elif 'manifest' in req.req.qsparams:
+changeid = req.req.qsparams['manifest']
 
 return changeidctx(repo, changeid)
 
 def basechangectx(repo, req):
-if 'node' in req.form:
-changeid = req.form['node'][0]
+if 'node' in req.req.qsparams:
+changeid = req.req.qsparams['node']
 ipos = changeid.find(':')
 if ipos != -1:
 changeid = changeid[:ipos]
 return changeidctx(repo, changeid)
 
 return None
 
 def filectx(repo, req):
-if 'file' not in req.form:
+if 'file' not in req.req.qsparams:
 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
-path = cleanpath(repo, req.form['file'][0])
-if 'node' in req.form:
-changeid = req.form['node'][0]
-elif 'filenode' in req.form:
-changeid = req.form['filenode'][0]
+path = cleanpath(repo, req.req.qsparams['file'])
+if 'node' in req.req.qsparams:
+changeid = req.req.qsparams['node']
+elif 'filenode' in req.req.qsparams:
+changeid = req.req.qsparams['filenode']
 else:
 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
 try:
@@ -333,8 +333,8 @@
 return fctx
 
 def linerange(req):
-linerange = req.form.get('linerange')
-if linerange is None:
+linerange = req.req.qsparams.getall('linerange')
+if not linerange:
 return None
 if len(linerange) > 1:
 raise ErrorResponse(HTTP_BAD_REQUEST,
@@ -412,8 +412,8 @@
 return entry
 
 def symrevorshortnode(req, ctx):
-if 'node' in req.form:
-return templatefilters.revescape(req.form['node'][0])
+if 'node' in req.req.qsparams:
+return templatefilters.revescape(req.req.qsparams['node'])
 else:
 return short(ctx.node())
 
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -85,16 +85,16 @@
 file will be shown. This form is equivalent to the ``filelog`` handler.
 """
 
-if 'file' in req.form and req.form['file'][0]:
+if req.req.qsparams.get('file'):
 return filelog(web, req, tmpl)
 else:
 return changelog(web, req, tmpl)
 
 @webcommand('rawfile')
 def rawfile(web, req, tmpl):
 guessmime = web.configbool('web', 'guessmime')
 
-path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
+path = webutil.cleanpath(web.repo, req.req.qsparams.get('file', ''))
 if not path:
 content = manifest(web, req, tmpl)
 req.respond(HTTP_OK, web.ctype)
@@ -173,7 +173,7 @@
 If ``path`` is not defined, information about the root directory will
 be rendered.
 """
-path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
+path = webutil.cleanpath(web.repo, req.req.

D2780: hgweb: set variables in qsparams

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We currently mutate wsgireq.form in a few places. Since it is
  independent from req.qsparams, we will need to make changes on
  req.qsparams as well before consumers can use qsparams. So let's
  do that.
  
  Eventually, we'll delete wsgireq.form and all references to it.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2780

AFFECTED FILES
  mercurial/hgweb/hgweb_mod.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -342,15 +342,22 @@
 # avoid accepting e.g. style parameter as command
 if util.safehasattr(webcommands, cmd):
 wsgireq.form['cmd'] = [cmd]
+req.qsparams['cmd'] = cmd
 
 if cmd == 'static':
 wsgireq.form['file'] = ['/'.join(args)]
+req.qsparams['file'] = '/'.join(args)
 else:
 if args and args[0]:
 node = args.pop(0).replace('%2F', '/')
 wsgireq.form['node'] = [node]
+req.qsparams['node'] = node
 if args:
 wsgireq.form['file'] = args
+if 'file' in req.qsparams:
+del req.qsparams['file']
+for a in args:
+req.qsparams.add('file', a)
 
 ua = req.headers.get('User-Agent', '')
 if cmd == 'rev' and 'mercurial' in ua:
@@ -362,7 +369,9 @@
 ext = spec[2]
 if fn.endswith(ext):
 wsgireq.form['node'] = [fn[:-len(ext)]]
+req.qsparams['node'] = fn[:-len(next)]
 wsgireq.form['type'] = [type_]
+req.qsparams['type'] = type_
 else:
 cmd = wsgireq.form.get('cmd', [''])[0]
 
@@ -379,6 +388,7 @@
 
 if cmd == '':
 wsgireq.form['cmd'] = [tmpl.cache['default']]
+req.qsparams['cmd'] = tmpl.cache['default']
 cmd = wsgireq.form['cmd'][0]
 
 # Don't enable caching if using a CSP nonce because then it 
wouldn't



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


D2775: hgweb: create dedicated type for WSGI responses

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6837.
indygreg edited the summary of this revision.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2775?vs=6821&id=6837

REVISION DETAIL
  https://phab.mercurial-scm.org/D2775

AFFECTED FILES
  mercurial/hgweb/hgweb_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
@@ -149,18 +149,18 @@
 def iscmd(cmd):
 return cmd in wireproto.commands
 
-def handlewsgirequest(rctx, wsgireq, req, checkperm):
+def handlewsgirequest(rctx, wsgireq, req, res, checkperm):
 """Possibly process a wire protocol request.
 
 If the current request is a wire protocol request, the request is
 processed by this function.
 
 ``wsgireq`` is a ``wsgirequest`` instance.
 ``req`` is a ``parsedrequest`` instance.
+``res`` is a ``wsgiresponse`` instance.
 
-Returns a 2-tuple of (bool, response) where the 1st element indicates
-whether the request was handled and the 2nd element is a return
-value for a WSGI application (often a generator of bytes).
+Returns a bool indicating if the request was serviced. If set, the caller
+should stop processing the request, as a response has already been issued.
 """
 # Avoid cycle involving hg module.
 from .hgweb import common as hgwebcommon
@@ -171,7 +171,7 @@
 # string parameter. If it isn't present, this isn't a wire protocol
 # request.
 if 'cmd' not in req.querystringdict:
-return False, None
+return False
 
 cmd = req.querystringdict['cmd'][0]
 
@@ -183,18 +183,19 @@
 # known wire protocol commands and it is less confusing for machine
 # clients.
 if not iscmd(cmd):
-return False, None
+return False
 
 # The "cmd" query string argument is only valid on the root path of the
 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
 # in this case. We send an HTTP 404 for backwards compatibility reasons.
 if req.dispatchpath:
-res = _handlehttperror(
-hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq,
-req)
-
-return True, res
+res.status = hgwebcommon.statusmessage(404)
+res.headers['Content-Type'] = HGTYPE
+# TODO This is not a good response to issue for this request. This
+# is mostly for BC for now.
+res.setbodybytes('0\n%s\n' % b'Not Found')
+return True
 
 proto = httpv1protocolhandler(wsgireq, req, repo.ui,
   lambda perm: checkperm(rctx, wsgireq, perm))
@@ -204,11 +205,16 @@
 # exception here. So consider refactoring into a exception type that
 # is associated with the wire protocol.
 try:
-res = _callhttp(repo, wsgireq, req, proto, cmd)
+_callhttp(repo, wsgireq, req, res, proto, cmd)
 except hgwebcommon.ErrorResponse as e:
-res = _handlehttperror(e, wsgireq, req)
+for k, v in e.headers:
+res.headers[k] = v
+res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
+# TODO This response body assumes the failed command was
+# "unbundle." That assumption is not always valid.
+res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
 
-return True, res
+return True
 
 def _httpresponsetype(ui, req, prefer_uncompressed):
 """Determine the appropriate response type and compression settings.
@@ -250,7 +256,10 @@
 opts = {'level': ui.configint('server', 'zliblevel')}
 return HGTYPE, util.compengines['zlib'], opts
 
-def _callhttp(repo, wsgireq, req, proto, cmd):
+def _callhttp(repo, wsgireq, req, res, proto, cmd):
+# Avoid cycle involving hg module.
+from .hgweb import common as hgwebcommon
+
 def genversion2(gen, engine, engineopts):
 # application/mercurial-0.2 always sends a payload header
 # identifying the compression engine.
@@ -262,26 +271,35 @@
 for chunk in gen:
 yield chunk
 
+def setresponse(code, contenttype, bodybytes=None, bodygen=None):
+if code == HTTP_OK:
+res.status = '200 Script output follows'
+else:
+res.status = hgwebcommon.statusmessage(code)
+
+res.headers['Content-Type'] = contenttype
+
+if bodybytes is not None:
+res.setbodybytes(bodybytes)
+if bodygen is not None:
+res.setbodygen(bodygen)
+
 if not wireproto.commands.commandavailable(cmd, proto):
-wsgireq.respond(HTTP_OK, HGERRTYPE,
-body=_('requested wire protocol command is not '
-   'available over HTTP'))
-return []
+setresponse(HTTP_OK, HGERRTYPE,
+_('reques

D2773: hgweb: remove support for short query string based aliases (BC)

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6835.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2773?vs=6820&id=6835

REVISION DETAIL
  https://phab.mercurial-scm.org/D2773

AFFECTED FILES
  mercurial/hgweb/request.py
  tests/test-hgweb-raw.t

CHANGE DETAILS

diff --git a/tests/test-hgweb-raw.t b/tests/test-hgweb-raw.t
--- a/tests/test-hgweb-raw.t
+++ b/tests/test-hgweb-raw.t
@@ -17,7 +17,7 @@
   $ hg serve -p $HGPORT -A access.log -E error.log -d --pid-file=hg.pid
 
   $ cat hg.pid >> $DAEMON_PIDS
-  $ (get-with-headers.py localhost:$HGPORT 
'?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type 
content-length content-disposition) >getoutput.txt
+  $ (get-with-headers.py localhost:$HGPORT 
'raw-file/bf0ff59095c9/sub/some%20text%25.txt' content-type content-length 
content-disposition) >getoutput.txt
 
   $ killdaemons.py hg.pid
 
@@ -32,14 +32,14 @@
   It is very boring to read, but computers don't
   care about things like that.
   $ cat access.log error.log
-  $LOCALIP - - [*] "GET /?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw 
HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /raw-file/bf0ff59095c9/sub/some%20text%25.txt 
HTTP/1.1" 200 - (glob)
 
   $ rm access.log error.log
   $ hg serve -p $HGPORT -A access.log -E error.log -d --pid-file=hg.pid \
   > --config web.guessmime=True
 
   $ cat hg.pid >> $DAEMON_PIDS
-  $ (get-with-headers.py localhost:$HGPORT 
'?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type 
content-length content-disposition) >getoutput.txt
+  $ (get-with-headers.py localhost:$HGPORT 
'raw-file/bf0ff59095c9/sub/some%20text%25.txt' content-type content-length 
content-disposition) >getoutput.txt
   $ killdaemons.py hg.pid
 
   $ cat getoutput.txt
@@ -53,6 +53,6 @@
   It is very boring to read, but computers don't
   care about things like that.
   $ cat access.log error.log
-  $LOCALIP - - [*] "GET /?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw 
HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /raw-file/bf0ff59095c9/sub/some%20text%25.txt 
HTTP/1.1" 200 - (glob)
 
   $ cd ..
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -27,37 +27,6 @@
 util,
 )
 
-shortcuts = {
-'cl': [('cmd', ['changelog']), ('rev', None)],
-'sl': [('cmd', ['shortlog']), ('rev', None)],
-'cs': [('cmd', ['changeset']), ('node', None)],
-'f': [('cmd', ['file']), ('filenode', None)],
-'fl': [('cmd', ['filelog']), ('filenode', None)],
-'fd': [('cmd', ['filediff']), ('node', None)],
-'fa': [('cmd', ['annotate']), ('filenode', None)],
-'mf': [('cmd', ['manifest']), ('manifest', None)],
-'ca': [('cmd', ['archive']), ('node', None)],
-'tags': [('cmd', ['tags'])],
-'tip': [('cmd', ['changeset']), ('node', ['tip'])],
-'static': [('cmd', ['static']), ('file', None)]
-}
-
-def normalize(form):
-# first expand the shortcuts
-for k in shortcuts:
-if k in form:
-for name, value in shortcuts[k]:
-if value is None:
-value = form[k]
-form[name] = value
-del form[k]
-# And strip the values
-bytesform = {}
-for k, v in form.iteritems():
-bytesform[pycompat.bytesurl(k)] = [
-pycompat.bytesurl(i.strip()) for i in v]
-return bytesform
-
 @attr.s(frozen=True)
 class parsedrequest(object):
 """Represents a parsed WSGI request.
@@ -258,7 +227,7 @@
 self.run_once = wsgienv[r'wsgi.run_once']
 self.env = wsgienv
 self.req = parserequestfromenv(wsgienv, inp)
-self.form = normalize(self.req.querystringdict)
+self.form = self.req.querystringdict
 self._start_response = start_response
 self.server_write = None
 self.headers = []



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


D2745: hgweb: store and use request method on parsed request

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6825.
indygreg edited the summary of this revision.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2745?vs=6751&id=6825

REVISION DETAIL
  https://phab.mercurial-scm.org/D2745

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
@@ -324,7 +324,7 @@
 # the HTTP response. In other words, it helps prevent deadlocks
 # on clients using httplib.
 
-if (wsgireq.env[r'REQUEST_METHOD'] == r'POST' and
+if (req.method == 'POST' and
 # But not if Expect: 100-continue is being used.
 (req.headers.get('Expect', '').lower() != '100-continue')):
 wsgireq.drain()
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -63,6 +63,8 @@
 class parsedrequest(object):
 """Represents a parsed WSGI request / static HTTP request parameters."""
 
+# Request method.
+method = attr.ib()
 # Full URL for this request.
 url = attr.ib()
 # URL without any path components. Just ://.
@@ -207,7 +209,8 @@
 if 'CONTENT_LENGTH' in env and 'HTTP_CONTENT_LENGTH' not in env:
 headers['Content-Length'] = env['CONTENT_LENGTH']
 
-return parsedrequest(url=fullurl, baseurl=baseurl,
+return parsedrequest(method=env['REQUEST_METHOD'],
+ url=fullurl, baseurl=baseurl,
  advertisedurl=advertisedfullurl,
  advertisedbaseurl=advertisedbaseurl,
  apppath=apppath,



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-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6826.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2746?vs=6752&id=6826

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


D2749: hgweb: remove wsgirequest.__iter__

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6829.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2749?vs=6755&id=6829

REVISION DETAIL
  https://phab.mercurial-scm.org/D2749

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
@@ -246,9 +246,6 @@
 self.server_write = None
 self.headers = []
 
-def __iter__(self):
-return iter([])
-
 def drain(self):
 '''need to read all data from request, httplib is half-duplex'''
 length = int(self.env.get('CONTENT_LENGTH') or 0)



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-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6827.
indygreg edited the summary of this revision.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2747?vs=6753&id=6827

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


D2744: hgweb: handle CONTENT_LENGTH

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6824.
indygreg edited the summary of this revision.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2744?vs=6750&id=6824

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: durin42, 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-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6834.
indygreg retitled this revision from "hgweb: remove support for retrieving 
parameters from POST form data" to "hgweb: remove support for POST form data 
(BC)".

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2774?vs=6819&id=6834

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
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


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

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6830.
indygreg edited the summary of this revision.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2768?vs=6812&id=6830

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
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-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6833.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2771?vs=6815&id=6833

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 reading until EOF won't corrupt the stream
 # state.
-if not isinstance(self.inp, util.cappedreader):
+if not is

D2776: hgweb: use a multidict for holding query string parameters

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6838.
indygreg edited the summary of this revision.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2776?vs=6822&id=6838

REVISION DETAIL
  https://phab.mercurial-scm.org/D2776

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
@@ -170,10 +170,10 @@
 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
 # string parameter. If it isn't present, this isn't a wire protocol
 # request.
-if 'cmd' not in req.querystringdict:
+if 'cmd' not in req.qsparams:
 return False
 
-cmd = req.querystringdict['cmd'][0]
+cmd = req.qsparams['cmd']
 
 # The "cmd" request parameter is used by both the wire protocol and hgweb.
 # While not all wire protocol commands are available for all transports,
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -28,6 +28,90 @@
 util,
 )
 
+class multidict(object):
+"""A dict like object that can store multiple values for a key.
+
+Used to store parsed request parameters.
+
+This is inspired by WebOb's class of the same name.
+"""
+def __init__(self):
+# Stores (key, value) 2-tuples. This isn't the most efficient. But we
+# don't rely on parameters that much, so it shouldn't be a perf issue.
+# we can always add dict for fast lookups.
+self._items = []
+
+def __getitem__(self, key):
+"""Returns the last set value for a key."""
+for k, v in reversed(self._items):
+if k == key:
+return v
+
+raise KeyError(key)
+
+def __setitem__(self, key, value):
+"""Replace a values for a key with a new value."""
+try:
+del self[key]
+except KeyError:
+pass
+
+self._items.append((key, value))
+
+def __delitem__(self, key):
+"""Delete all values for a key."""
+oldlen = len(self._items)
+
+self._items[:] = [(k, v) for k, v in self._items if k != key]
+
+if oldlen == len(self._items):
+raise KeyError(key)
+
+def __contains__(self, key):
+return any(k == key for k, v in self._items)
+
+def __len__(self):
+return len(self._items)
+
+def get(self, key, default=None):
+try:
+return self.__getitem__(key)
+except KeyError:
+return default
+
+def add(self, key, value):
+"""Add a new value for a key. Does not replace existing values."""
+self._items.append((key, value))
+
+def getall(self, key):
+"""Obtains all values for a key."""
+return [v for k, v in self._items if k == key]
+
+def getone(self, key):
+"""Obtain a single value for a key.
+
+Raises KeyError if key not defined or it has multiple values set.
+"""
+vals = self.getall(key)
+
+if not vals:
+raise KeyError(key)
+
+if len(vals) > 1:
+raise KeyError('multiple values for %r' % key)
+
+return vals[0]
+
+def asdictoflists(self):
+d = {}
+for k, v in self._items:
+if k in d:
+d[k].append(v)
+else:
+d[k] = [v]
+
+return d
+
 @attr.s(frozen=True)
 class parsedrequest(object):
 """Represents a parsed WSGI request.
@@ -56,10 +140,8 @@
 havepathinfo = attr.ib()
 # Raw query string (part after "?" in URL).
 querystring = attr.ib()
-# List of 2-tuples of query string arguments.
-querystringlist = attr.ib()
-# Dict of query string arguments. Values are lists with at least 1 item.
-querystringdict = attr.ib()
+# multidict of query string parameters.
+qsparams = attr.ib()
 # wsgiref.headers.Headers instance. Operates like a dict with case
 # insensitive keys.
 headers = attr.ib()
@@ -157,14 +239,9 @@
 
 # We store as a list so we have ordering information. We also store as
 # a dict to facilitate fast lookup.
-querystringlist = util.urlreq.parseqsl(querystring, keep_blank_values=True)
-
-querystringdict = {}
-for k, v in querystringlist:
-if k in querystringdict:
-querystringdict[k].append(v)
-else:
-querystringdict[k] = [v]
+qsparams = multidict()
+for k, v in util.urlreq.parseqsl(querystring, keep_blank_values=True):
+qsparams.add(k, v)
 
 # HTTP_* keys contain HTTP request headers. The Headers structure should
 # perform case normalization for us. We just rewrite underscore to dash
@@ -197,8 +274,7 @@
  dispatchparts=dispatchparts, 
dispatchpath=dispatchpath,
  havepathinfo='PATH_INFO' in env,

D2778: tests: add test for a wire protocol request to wrong base URL

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We have code that validates that wire protocol commands (which are
  specified via query string) must occur at the base URL of a repo.
  But we have no test coverage for this behavior. Let's add some.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2778

AFFECTED FILES
  tests/test-hgweb-commands.t

CHANGE DETAILS

diff --git a/tests/test-hgweb-commands.t b/tests/test-hgweb-commands.t
--- a/tests/test-hgweb-commands.t
+++ b/tests/test-hgweb-commands.t
@@ -1916,6 +1916,19 @@
   
   lookup branchmap pushkey known getbundle unbundlehash batch 
changegroupsubset $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN 
httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx 
compression=$BUNDLE2_COMPRESSIONS$
 
+wire protocol command to wrong base URL
+
+  $ get-with-headers.py $LOCALIP:$HGPORT 'foo?cmd=capabilities' -
+  404 Not Found
+  content-length: 12
+  content-type: application/mercurial-0.1
+  date: * (glob)
+  server: * (glob)
+  
+  0
+  Not Found
+  [1]
+
 heads
 
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=heads'



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


D2769: hgweb: refactor the request draining code

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6831.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2769?vs=6813&id=6831

REVISION DETAIL
  https://phab.mercurial-scm.org/D2769

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
@@ -301,9 +301,6 @@
 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
 return []
 elif isinstance(rsp, wireprototypes.pusherr):
-# This is the httplib workaround documented in _handlehttperror().
-wsgireq.drain()
-
 rsp = '0\n%s\n' % rsp.res
 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
 return []
@@ -316,21 +313,6 @@
 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
-# won't process an HTTP response until all request data is
-# sent to the server. The intent of this code is to ensure
-# we always read HTTP request data from the client, thus
-# ensuring httplib transitions to a state that allows it to read
-# the HTTP response. In other words, it helps prevent deadlocks
-# on clients using httplib.
-
-if (req.method == 'POST' and
-# But not if Expect: 100-continue is being used.
-(req.headers.get('Expect', '').lower() != '100-continue')):
-wsgireq.drain()
-else:
-wsgireq.headers.append((r'Connection', r'Close'))
-
 # TODO This response body assumes the failed command was
 # "unbundle." That assumption is not always valid.
 wsgireq.respond(e, HGTYPE, body='0\n%s\n' % pycompat.bytestr(e))
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -254,12 +254,6 @@
 self.server_write = None
 self.headers = []
 
-def drain(self):
-'''need to read all data from request, httplib is half-duplex'''
-length = int(self.env.get('CONTENT_LENGTH') or 0)
-for s in util.filechunkiter(self.inp, limit=length):
-pass
-
 def respond(self, status, type, filename=None, body=None):
 if not isinstance(type, str):
 type = pycompat.sysstr(type)
@@ -292,6 +286,53 @@
 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.inp, 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.inp, util.cappedreader)
+while True:
+chunk = self.inp.read(32768)
+if not chunk:
+break
+
 self.server_write = se

D2748: hgweb: remove wsgirequest.read()

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6828.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2748?vs=6754&id=6828

REVISION DETAIL
  https://phab.mercurial-scm.org/D2748

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
@@ -83,7 +83,7 @@
 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))
+self._wsgireq.inp.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, limit=length):
+for s in util.filechunkiter(self._wsgireq.inp, 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
@@ -249,9 +249,6 @@
 def __iter__(self):
 return iter([])
 
-def read(self, count=-1):
-return self.inp.read(count)
-
 def drain(self):
 '''need to read all data from request, httplib is half-duplex'''
 length = int(self.env.get('CONTENT_LENGTH') or 0)



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


D2779: hgweb: use our new request object for "style" parameter

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  The "style" parameter is kind of wonky because it is explicitly
  set and has lookups in random locations.
  
  Let's port it to qsparams first because it isn't straightforward.
  
  There is subtle change in behavior. But I don't think it is worth
  calling out in a BC.
  
  Our multidict's __getitem__ returns the last set value for a key,
  not the first. So if the query string set a variable multiple times,
  before we would get the first value and now we would get the last
  value. It makes no sense to specify these things multiple times.
  And I think last write wins is more sensible than first write wins.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2779

AFFECTED FILES
  mercurial/hgweb/hgweb_mod.py
  mercurial/hgweb/hgwebdir_mod.py
  mercurial/hgweb/webcommands.py
  mercurial/hgweb/webutil.py

CHANGE DETAILS

diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -438,8 +438,8 @@
 basectx = ctx.p1()
 
 style = web.config('web', 'style')
-if 'style' in req.form:
-style = req.form['style'][0]
+if 'style' in req.req.qsparams:
+style = req.req.qsparams['style']
 
 diff = diffs(web, tmpl, ctx, basectx, None, style)
 
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -762,8 +762,8 @@
 basectx = ctx.p1()
 
 style = web.config('web', 'style')
-if 'style' in req.form:
-style = req.form['style'][0]
+if 'style' in req.req.qsparams:
+style = req.req.qsparams['style']
 
 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
 if fctx is not None:
@@ -1011,8 +1011,8 @@
 entries = []
 
 diffstyle = web.config('web', 'style')
-if 'style' in req.form:
-diffstyle = req.form['style'][0]
+if 'style' in req.req.qsparams:
+diffstyle = req.req.qsparams['style']
 
 def diff(fctx, linerange=None):
 ctx = fctx.changectx()
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
@@ -510,7 +510,7 @@
 url += '/'
 
 vars = {}
-styles, (style, mapfile) = hgweb_mod.getstyle(wsgireq, config,
+styles, (style, mapfile) = hgweb_mod.getstyle(wsgireq.req, config,
   self.templatepath)
 if style == styles[0]:
 vars['style'] = style
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
@@ -53,9 +53,8 @@
 ))
 
 def getstyle(req, configfn, templatepath):
-fromreq = req.form.get('style', [None])[0]
 styles = (
-fromreq,
+req.qsparams.get('style', None),
 configfn('web', 'style'),
 'paper',
 )
@@ -160,7 +159,7 @@
 # figure out which style to use
 
 vars = {}
-styles, (style, mapfile) = getstyle(wsgireq, self.config,
+styles, (style, mapfile) = getstyle(wsgireq.req, self.config,
 self.templatepath)
 if style == styles[0]:
 vars['style'] = style
@@ -337,7 +336,7 @@
 cmd = args.pop(0)
 style = cmd.rfind('-')
 if style != -1:
-wsgireq.form['style'] = [cmd[:style]]
+req.qsparams['style'] = cmd[:style]
 cmd = cmd[style + 1:]
 
 # avoid accepting e.g. style parameter as command
@@ -355,7 +354,7 @@
 
 ua = req.headers.get('User-Agent', '')
 if cmd == 'rev' and 'mercurial' in ua:
-wsgireq.form['style'] = ['raw']
+req.qsparams['style'] = 'raw'
 
 if cmd == 'archive':
 fn = wsgireq.form['node'][0]
@@ -389,7 +388,7 @@
 if cmd not in webcommands.__all__:
 msg = 'no such method: %s' % cmd
 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
-elif cmd == 'file' and 'raw' in wsgireq.form.get('style', []):
+elif cmd == 'file' and req.qsparams.get('style') == 'raw':
 rctx.ctype = ctype
 content = webcommands.rawfile(rctx, wsgireq, tmpl)
 else:



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


D2770: hgweb: make parsedrequest part of wsgirequest

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6832.
indygreg edited the summary of this revision.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2770?vs=6814&id=6832

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
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2720: debugcommands: introduce actions to perform deterministic reads

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg added a comment.


  See the `contrib/showstack.py` extension for registering SIGQUIT and SIGINFO 
signals to dump the active stack. Not sure if that works on Windows though.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2720

To: indygreg, #hg-reviewers
Cc: mharbison72, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2777: wireproto: raise ProgrammingError instead of Abort

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This isn't a user-facing error and can only be caused by bad
  Python code.
  
  Thanks to Yuya for spotting this.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2777

AFFECTED FILES
  mercurial/wireproto.py

CHANGE DETAILS

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -704,12 +704,13 @@
 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
   if v['version'] == 2}
 else:
-raise error.Abort(_('invalid transport policy value: %s') %
-  transportpolicy)
+raise error.ProgrammingError('invalid transport policy value: %s' %
+ transportpolicy)
 
 if permission not in ('push', 'pull'):
-raise error.Abort(_('invalid wire protocol permission; got %s; '
-'expected "push" or "pull"') % permission)
+raise error.ProgrammingError('invalid wire protocol permission; '
+ 'got %s; expected "push" or "pull"' %
+ permission)
 
 def register(func):
 commands[name] = commandentry(func, args=args, transports=transports,



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


D2720: debugcommands: introduce actions to perform deterministic reads

2018-03-10 Thread mharbison72 (Matt Harbison)
mharbison72 added a comment.


  At this point, I've narrowed it down to stderr.  I misunderstood the 
'noreadstderr', and wondered why we were bothering to pipe stderr if True.  So 
I set it to os.devnull, and the test sails through.  (But it does drop all "e>" 
lines, so that's not an actual solution.)
  
  Is there some way to send a signal to a python process to get it to dump its 
stacks, like Ctrl+\ in java?  Sprinkling random prints and flushes hasn't been 
fruitful so far.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2720

To: indygreg, #hg-reviewers
Cc: mharbison72, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2734: hgweb: parse WSGI request into a data structure

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg marked 4 inline comments as done.
indygreg added a comment.


  I amended hg-committed with the fixes for the issues @yuja found.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2734

To: indygreg, #hg-reviewers, durin42
Cc: pulkit, yuja, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2734: hgweb: parse WSGI request into a data structure

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg added inline comments.

INLINE COMMENTS

> pulkit wrote in request.py:14
> Chunk from your wip?

Yes. But there is a TODO below to track enabling it. I fully intend to enable 
this once we can. That will likely require doing away with the REPO_NAME hack 
in hgwebdir. That will likely happen towards the end of this series. I'm a few 
hours away from getting there I think :)

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2734

To: indygreg, #hg-reviewers, durin42
Cc: pulkit, yuja, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH 1 of 2] util: forward __bool__()/__nonzero__() on fileobjectproxy

2018-03-10 Thread Gregory Szorc
On Sat, Mar 10, 2018 at 10:05 AM, Matt Harbison 
wrote:

> # HG changeset patch
> # User Matt Harbison 
> # Date 1520702528 18000
> #  Sat Mar 10 12:22:08 2018 -0500
> # Node ID e21d0c21f81315d012c4806359bc76aa40c89ad6
> # Parent  963b4223d14fa419df2a82fbe47cd55075707b6a
> util: forward __bool__()/__nonzero__() on fileobjectproxy
>

Good catch.

Queued, thanks.

Proxy objects are hard. FWIW, the most robust implementation I've seen is
http://wrapt.readthedocs.io/en/latest/. If you look at the source code and
bug history of that project, there is some serious magic required to
properly implement a proxy object! If we ever use proxies more seriously in
core, we should consider vendoring wrapt.


>
> In trying to debug the Windows process hang in D2720, I changed the stderr
> pipe
> to the peer to be os.devnull instead.  That caused
> sshpeer._cleanuppipes()[1] to
> explode, complaining NoneType has no __iter__ attribute, even though the
> previous line checked for None.
>
> [1] https://www.mercurial-scm.org/repo/hg/file/b434965f984e/
> mercurial/sshpeer.py#l133
>
> diff --git a/mercurial/util.py b/mercurial/util.py
> --- a/mercurial/util.py
> +++ b/mercurial/util.py
> @@ -551,6 +551,11 @@ class fileobjectproxy(object):
>
>  return getattr(object.__getattribute__(self, r'_orig'), name)
>
> +def __nonzero__(self):
> +return bool(object.__getattribute__(self, r'_orig'))
> +
> +__bool__ = __nonzero__
> +
>  def __delattr__(self, name):
>  return delattr(object.__getattribute__(self, r'_orig'), name)
>
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
>
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 2] sshpeer: check pipe validity before forwarding output from it

2018-03-10 Thread Matt Harbison
# HG changeset patch
# User Matt Harbison 
# Date 1520703910 18000
#  Sat Mar 10 12:45:10 2018 -0500
# Node ID 0e076d66888bff8e3eac166454bb3214c48ff8be
# Parent  e21d0c21f81315d012c4806359bc76aa40c89ad6
sshpeer: check pipe validity before forwarding output from it

After the previous fix, fileobjectproxy._observedcall() (called when
win32.peekpipe() accesses .fileno) started exploding.  With this fix, similar
checks are needed inside debugwireproto().  Since that is hardcoded to not use
os.devnull, IDK if those are worth fixing.

diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -32,10 +32,11 @@ def _forwardoutput(ui, pipe):
 """display all data currently available on pipe as remote output.
 
 This is non blocking."""
-s = util.readpipe(pipe)
-if s:
-for l in s.splitlines():
-ui.status(_("remote: "), l, '\n')
+if pipe:
+s = util.readpipe(pipe)
+if s:
+for l in s.splitlines():
+ui.status(_("remote: "), l, '\n')
 
 class doublepipe(object):
 """Operate a side-channel pipe in addition of a main one
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 2] util: forward __bool__()/__nonzero__() on fileobjectproxy

2018-03-10 Thread Matt Harbison
# HG changeset patch
# User Matt Harbison 
# Date 1520702528 18000
#  Sat Mar 10 12:22:08 2018 -0500
# Node ID e21d0c21f81315d012c4806359bc76aa40c89ad6
# Parent  963b4223d14fa419df2a82fbe47cd55075707b6a
util: forward __bool__()/__nonzero__() on fileobjectproxy

In trying to debug the Windows process hang in D2720, I changed the stderr pipe
to the peer to be os.devnull instead.  That caused sshpeer._cleanuppipes()[1] to
explode, complaining NoneType has no __iter__ attribute, even though the
previous line checked for None.

[1] 
https://www.mercurial-scm.org/repo/hg/file/b434965f984e/mercurial/sshpeer.py#l133

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -551,6 +551,11 @@ class fileobjectproxy(object):
 
 return getattr(object.__getattribute__(self, r'_orig'), name)
 
+def __nonzero__(self):
+return bool(object.__getattribute__(self, r'_orig'))
+
+__bool__ = __nonzero__
+
 def __delattr__(self, name):
 return delattr(object.__getattribute__(self, r'_orig'), name)
 
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[Bug 5812] New: paper theme uses inline javascript, making web.csp far less effective

2018-03-10 Thread mercurial-bugs
https://bz.mercurial-scm.org/show_bug.cgi?id=5812

Bug ID: 5812
   Summary: paper theme uses inline javascript, making web.csp far
less effective
   Product: Mercurial
   Version: 4.5.2
  Hardware: All
OS: All
Status: UNCONFIRMED
  Severity: feature
  Priority: wish
 Component: hgweb
  Assignee: bugzi...@mercurial-scm.org
  Reporter: a...@dwimlabs.net
CC: mercurial-devel@mercurial-scm.org

$ grep 'javascript:' -rn mercurial/templates/
paper/filediff.tmpl:68:line wrap: on
paper/filerevision.tmpl:68:line wrap:
on
paper/changeset.tmpl:72:[+]
paper/changeset.tmpl:74:  [-]
paper/changeset.tmpl:82:line wrap: on

While 

Re: [PATCH] ui: remove any combinations of CR|LF from prompt response

2018-03-10 Thread Matt Harbison

On Sat, 10 Mar 2018 02:52:00 -0500, Yuya Nishihara  wrote:


# HG changeset patch
# User Yuya Nishihara 
# Date 1520664609 -32400
#  Sat Mar 10 15:50:09 2018 +0900
# Node ID c38b2b364df79a9defc3520f19207ce47abcc7d8
# Parent  9ddc9aa26801bac571bd3413a8aed900c2d2efb8
ui: remove any combinations of CR|LF from prompt response


I didn't run the whole test suite, but this fixes the ones that broke.   
Thanks.

___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2718: wireproto: declare permissions requirements in @wireprotocommand (API)

2018-03-10 Thread indygreg (Gregory Szorc)
indygreg added inline comments.

INLINE COMMENTS

> yuja wrote in wireproto.py:711
> Perhaps ProgrammingError is preferred here because it shouldn't
> be a user-facing error.

I agree. I'll send a follow-up (unless we want to amend hg-committed).

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2718

To: indygreg, #hg-reviewers, durin42
Cc: yuja, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2394: histedit: make histedit's commands accept revsets (issue5746)

2018-03-10 Thread sangeet259 (Sangeet Kumar Mishra)
sangeet259 added a comment.


  @pulkit  It is not failing as of now. It's failing on the changes proposed by 
@durin42, which he assumed wont fail, but that's not the case!

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2394

To: sangeet259, durin42, #hg-reviewers
Cc: pulkit, tom.prince, krbullock, rishabhmadan96, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 5] py3: drop b'' from debug message "moving bookmarks"

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520342652 21600
#  Tue Mar 06 07:24:12 2018 -0600
# Node ID 6c476e5b05b821b9e6b8da9de807ca45940b6fad
# Parent  fca950cc0de8a69d23bf37521e43cc5310009aad
py3: drop b'' from debug message "moving bookmarks"

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -25,6 +25,7 @@ test-bookmarks-merge.t
 test-bookmarks-rebase.t
 test-bookmarks-strip.t
 test-bookmarks.t
+test-branch-change.t
 test-branch-option.t
 test-branch-tag-confict.t
 test-branches.t
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -689,7 +689,8 @@ def cleanupnodes(repo, replacements, ope
 continue
 from . import bookmarks # avoid import cycle
 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
-  (oldbmarks, hex(oldnode), hex(newnode)))
+  (util.rapply(pycompat.maybebytestr, oldbmarks),
+   hex(oldnode), hex(newnode)))
 # Delete divergent bookmarks being parents of related newnodes
 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - 
parents(%n)',
allnewnodes, newnode, oldnode)
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 5] py3: use r'' instead of sysstr('') to get around code transformer

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520665036 -32400
#  Sat Mar 10 15:57:16 2018 +0900
# Node ID fca950cc0de8a69d23bf37521e43cc5310009aad
# Parent  ebabfc339ee4fe88ae7ff96bc925d66512066da0
py3: use r'' instead of sysstr('') to get around code transformer

Fewer function calls should be better.

diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -1310,7 +1310,7 @@ class svn_sink(converter_sink, commandli
 self.setexec = []
 
 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
-fp = os.fdopen(fd, pycompat.sysstr('wb'))
+fp = os.fdopen(fd, r'wb')
 fp.write(util.tonativeeol(commit.desc))
 fp.close()
 try:
diff --git a/hgext/gpg.py b/hgext/gpg.py
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -60,11 +60,11 @@ class gpg(object):
 try:
 # create temporary files
 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
-fp = os.fdopen(fd, pycompat.sysstr('wb'))
+fp = os.fdopen(fd, r'wb')
 fp.write(sig)
 fp.close()
 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
-fp = os.fdopen(fd, pycompat.sysstr('wb'))
+fp = os.fdopen(fd, r'wb')
 fp.write(data)
 fp.close()
 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -212,7 +212,7 @@ class transplanter(object):
 patchfile = None
 else:
 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
-fp = os.fdopen(fd, pycompat.sysstr('wb'))
+fp = os.fdopen(fd, r'wb')
 gen = patch.diff(source, parent, node, opts=diffopts)
 for chunk in gen:
 fp.write(chunk)
@@ -260,7 +260,7 @@ class transplanter(object):
 self.ui.status(_('filtering %s\n') % patchfile)
 user, date, msg = (changelog[1], changelog[2], changelog[4])
 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
-fp = os.fdopen(fd, pycompat.sysstr('wb'))
+fp = os.fdopen(fd, r'wb')
 fp.write("# HG changeset patch\n")
 fp.write("# User %s\n" % user)
 fp.write("# Date %d %d\n" % date)
diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -349,7 +349,7 @@ class bundlerepository(localrepo.localre
 suffix=suffix)
 self.tempfile = temp
 
-with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
+with os.fdopen(fdtemp, r'wb') as fptemp:
 fptemp.write(header)
 while True:
 chunk = readfn(2**18)
diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -75,7 +75,7 @@ def writechunks(ui, chunks, filename, vf
 fh = open(filename, "wb", 131072)
 else:
 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
-fh = os.fdopen(fd, pycompat.sysstr("wb"))
+fh = os.fdopen(fd, r"wb")
 cleanup = filename
 for c in chunks:
 fh.write(c)
diff --git a/mercurial/chgserver.py b/mercurial/chgserver.py
--- a/mercurial/chgserver.py
+++ b/mercurial/chgserver.py
@@ -296,9 +296,9 @@ class channeledsystem(object):
 
 _iochannels = [
 # server.ch, ui.fp, mode
-('cin', 'fin', pycompat.sysstr('rb')),
-('cout', 'fout', pycompat.sysstr('wb')),
-('cerr', 'ferr', pycompat.sysstr('wb')),
+('cin', 'fin', r'rb'),
+('cout', 'fout', r'wb'),
+('cerr', 'ferr', r'wb'),
 ]
 
 class chgcmdserver(commandserver.server):
diff --git a/mercurial/commandserver.py b/mercurial/commandserver.py
--- a/mercurial/commandserver.py
+++ b/mercurial/commandserver.py
@@ -303,8 +303,8 @@ def _protectio(ui):
 ui.flush()
 newfiles = []
 nullfd = os.open(os.devnull, os.O_RDWR)
-for f, sysf, mode in [(ui.fin, util.stdin, pycompat.sysstr('rb')),
-  (ui.fout, util.stdout, pycompat.sysstr('wb'))]:
+for f, sysf, mode in [(ui.fin, util.stdin, r'rb'),
+  (ui.fout, util.stdout, r'wb')]:
 if f is sysf:
 newfd = os.dup(f.fileno())
 os.dup2(nullfd, f.fileno())
diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -1119,7 +1119,7 @@ def debuginstall(ui, **opts):
 
 def writetemp(contents):
 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
-f = os.fdopen(fd, pycompat.sysstr("wb"))
+f = os.fdopen(fd, r"wb")
 f.write(contents)
 f.close

[PATCH 3 of 5] py3: wrap file object to write patch in native eol preserving byte-ness

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520343957 21600
#  Tue Mar 06 07:45:57 2018 -0600
# Node ID d472860cb611fb3f268ae48bd2870432387eca5b
# Parent  6c476e5b05b821b9e6b8da9de807ca45940b6fad
py3: wrap file object to write patch in native eol preserving byte-ness

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1102,11 +1102,11 @@ all lines of the hunk are removed, then 
 the hunk is left unchanged.
 """)
 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
-suffix=".diff", text=True)
+  suffix=".diff")
 ncpatchfp = None
 try:
 # Write the initial patch
-f = os.fdopen(patchfd, r"w")
+f = util.nativeeolwriter(os.fdopen(patchfd, r'wb'))
 chunk.header.write(f)
 chunk.write(f)
 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -2421,6 +2421,22 @@ bytecount = unitcountfn(
 (1, 1, _('%.0f bytes')),
 )
 
+class transformingwriter(object):
+"""Writable file wrapper to transform data by function"""
+
+def __init__(self, fp, encode):
+self._fp = fp
+self._encode = encode
+
+def close(self):
+self._fp.close()
+
+def flush(self):
+self._fp.flush()
+
+def write(self, data):
+return self._fp.write(self._encode(data))
+
 # Matches a single EOL which can either be a CRLF where repeated CR
 # are removed or a LF. We do not care about old Macintosh files, so a
 # stray CR is an error.
@@ -2432,12 +2448,17 @@ def tolf(s):
 def tocrlf(s):
 return _eolre.sub('\r\n', s)
 
+def _crlfwriter(fp):
+return transformingwriter(fp, tocrlf)
+
 if pycompat.oslinesep == '\r\n':
 tonativeeol = tocrlf
 fromnativeeol = tolf
+nativeeolwriter = _crlfwriter
 else:
 tonativeeol = pycompat.identity
 fromnativeeol = pycompat.identity
+nativeeolwriter = pycompat.identity
 
 def escapestr(s):
 # call underlying function of s.encode('string_escape') directly for
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 4 of 5] py3: open patch file in binary mode and convert eol manually

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520678949 -32400
#  Sat Mar 10 19:49:09 2018 +0900
# Node ID a984ebd44ac74e7b532963a50e0714d93e4465e3
# Parent  d472860cb611fb3f268ae48bd2870432387eca5b
py3: open patch file in binary mode and convert eol manually

Here we don't introduce a reader wrapper since it wouldn't be easy to make
read(n) handle partial data and length correctly.

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1120,9 +1120,10 @@ the hunk is left unchanged.
 ui.warn(_("editor exited with exit code %d\n") % ret)
 continue
 # Remove comment lines
-patchfp = open(patchfn)
+patchfp = open(patchfn, r'rb')
 ncpatchfp = stringio()
 for line in util.iterfile(patchfp):
+line = util.fromnativeeol(line)
 if not line.startswith('#'):
 ncpatchfp.write(line)
 patchfp.close()
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 5 of 5] py3: make test-commit-interactive.t byte-safe

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520679407 -32400
#  Sat Mar 10 19:56:47 2018 +0900
# Node ID e287b170d17027721fa7afee87ad6bce248f0660
# Parent  a984ebd44ac74e7b532963a50e0714d93e4465e3
py3: make test-commit-interactive.t byte-safe

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -50,6 +50,7 @@ test-clone-r.t
 test-clone-update-order.t
 test-command-template.t
 test-commit-amend.t
+test-commit-interactive.t
 test-commit-multiple.t
 test-commit-unresolved.t
 test-commit.t
diff --git a/tests/test-commit-interactive.t b/tests/test-commit-interactive.t
--- a/tests/test-commit-interactive.t
+++ b/tests/test-commit-interactive.t
@@ -898,15 +898,18 @@ This tests that translated help message 
 
   $ cat > $TESTTMP/escape.py < from __future__ import absolute_import
-  > import sys
+  > from mercurial import (
+  > pycompat,
+  > util,
+  > )
   > def escape(c):
   > o = ord(c)
   > if o < 0x80:
   > return c
   > else:
-  > return r'\x%02x' % o # escape char setting MSB
-  > for l in sys.stdin:
-  > sys.stdout.write(''.join(escape(c) for c in l))
+  > return br'\x%02x' % o # escape char setting MSB
+  > for l in util.stdin:
+  > util.stdout.write(b''.join(escape(c) for c in pycompat.iterbytestr(l)))
   > EOF
 
   $ hg commit -i --encoding cp932 2>&1 

D2057: rust implementation of hg status

2018-03-10 Thread yuja (Yuya Nishihara)
yuja added a comment.


  > I am looking at Mozilla's rust winapi bindings, let me see if I can 
directly wrap around winapi::um::fileapi::FindFirstFileA 

  
  That's probably a hard way. I was thinking of something converting
  between OsStr (i.e. Path) and MBCS bytes by using Win32 API, instead
  of calling out the "A" API.
  
  
https://msdn.microsoft.com/en-us/library/windows/desktop/dd319072(v=vs.85).aspx
  
  We don't do that in Python, but Rust's type system will help making it right.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2057

To: Ivzhh, #hg-reviewers, kevincox
Cc: yuja, glandium, krbullock, indygreg, durin42, kevincox, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2734: hgweb: parse WSGI request into a data structure

2018-03-10 Thread pulkit (Pulkit Goyal)
pulkit added inline comments.

INLINE COMMENTS

> request.py:14
>  import socket
> +#import wsgiref.validate
>  

Chunk from your wip?

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2734

To: indygreg, #hg-reviewers, durin42
Cc: pulkit, yuja, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2734: hgweb: parse WSGI request into a data structure

2018-03-10 Thread yuja (Yuya Nishihara)
yuja added inline comments.

INLINE COMMENTS

> hgwebdir_mod.py:232
>  def _runwsgi(self, wsgireq):
> +req = requestmod.parserequestfromenv(wsgireq.env)
> +

We'll need something to silence pyflakes saying "local variable 'req' is
assigned to but never used."

> request.py:125
> +fullurl += env['SERVER_NAME']
> +addport(fullurl)
> +

Missed assignment `fullurl =` ?

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2734

To: indygreg, #hg-reviewers, durin42
Cc: yuja, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


mercurial@36788: 2 new changesets

2018-03-10 Thread Mercurial Commits
2 new changesets in mercurial:

https://www.mercurial-scm.org/repo/hg/rev/4397909f82d3
changeset:   36787:4397909f82d3
user:Tom Prince 
date:Sat Jan 20 02:41:10 2018 -0700
summary: phabricator: specify API tokens per host, rather than per repo

https://www.mercurial-scm.org/repo/hg/rev/b434965f984e
changeset:   36788:b434965f984e
bookmark:@
tag: tip
user:Augie Fackler 
date:Sun Mar 04 11:46:03 2018 -0500
summary: phabricator: follow-up phab auth improvements with backwards 
compat mode

-- 
Repository URL: https://www.mercurial-scm.org/repo/hg
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2409: graft: add no-commit mode (issue5631)

2018-03-10 Thread pulkit (Pulkit Goyal)
pulkit added a comment.


  In https://phab.mercurial-scm.org/D2409#43891, @khanchi97 wrote:
  
  > pulkit: Can you please review it? I have made the requested changes.
  
  
  I am sorry but I will like this to wait before we land the new state format 
thing. I don't want to put more information in old state files which don't have 
good format. But yes, if someone else feels we can go with this, I am fine with 
that too.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2409

To: khanchi97, #hg-reviewers
Cc: pulkit, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2394: histedit: make histedit's commands accept revsets (issue5746)

2018-03-10 Thread pulkit (Pulkit Goyal)
pulkit added a comment.


  In https://phab.mercurial-scm.org/D2394#44294, @sangeet259 wrote:
  
  > @durin42  Should I make any changes?
  
  
  Yep, you should try fixing the failures you mentioned.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2394

To: sangeet259, durin42, #hg-reviewers
Cc: pulkit, tom.prince, krbullock, rishabhmadan96, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH 1 of 5] py3: silence f.write() in test-annotate.t

2018-03-10 Thread Pulkit Goyal
Queued the series. Many thanks!

On Sat, Mar 10, 2018 at 1:41 PM, Yuya Nishihara  wrote:
> # HG changeset patch
> # User Yuya Nishihara 
> # Date 1520668554 -32400
> #  Sat Mar 10 16:55:54 2018 +0900
> # Node ID 7742cc1155b78a819ee1ecce7783c36bec42548e
> # Parent  a20a3f5171d4eacc6a2f801f0ce39516fb9fd444
> py3: silence f.write() in test-annotate.t
>
> diff --git a/tests/test-annotate.t b/tests/test-annotate.t
> --- a/tests/test-annotate.t
> +++ b/tests/test-annotate.t
> @@ -914,10 +914,10 @@ Annotate with orphaned CR (issue5798)
>> EOF
>
>>>> with open('a', 'wb') as f:
> -  ... f.write(b'0a\r0b\r\n0c\r0d\r\n0e\n0f\n0g')
> +  ... f.write(b'0a\r0b\r\n0c\r0d\r\n0e\n0f\n0g') and None
>$ hg ci -qAm0
>>>> with open('a', 'wb') as f:
> -  ... f.write(b'0a\r0b\r\n1c\r1d\r\n0e\n1f\n0g')
> +  ... f.write(b'0a\r0b\r\n1c\r1d\r\n0e\n1f\n0g') and None
>$ hg ci -m1
>
>$ hg annotate -r0 a | $PYTHON "$TESTTMP/substcr.py"
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2718: wireproto: declare permissions requirements in @wireprotocommand (API)

2018-03-10 Thread yuja (Yuya Nishihara)
yuja added inline comments.

INLINE COMMENTS

> wireproto.py:711
> +if permission not in ('push', 'pull'):
> +raise error.Abort(_('invalid wire protocol permission; got %s; '
> +'expected "push" or "pull"') % permission)

Perhaps ProgrammingError is preferred here because it shouldn't
be a user-facing error.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2718

To: indygreg, #hg-reviewers, durin42
Cc: yuja, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH V2] templatefilters: inline hbisect.shortlabel()

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520342101 21600
#  Tue Mar 06 07:15:01 2018 -0600
# Node ID 7463bb17ee1a9a5cdc5dbbf6ec370e15dac5b20b
# Parent  61f2121cac167752acc5014ac7f4c4da100a2097
templatefilters: inline hbisect.shortlabel()

It's pretty simple. I don't think the business logic has to be placed in
hbisect.py.

diff --git a/mercurial/hbisect.py b/mercurial/hbisect.py
--- a/mercurial/hbisect.py
+++ b/mercurial/hbisect.py
@@ -267,12 +267,6 @@ def label(repo, node):
 
 return None
 
-def shortlabel(label):
-if label:
-return label[0].upper()
-
-return None
-
 def printresult(ui, repo, state, displayer, nodes, good):
 if len(nodes) == 1:
 # narrowed it down to a single revision
diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
--- a/mercurial/templatefilters.py
+++ b/mercurial/templatefilters.py
@@ -14,7 +14,6 @@ import time
 from . import (
 encoding,
 error,
-hbisect,
 node,
 pycompat,
 registrar,
@@ -343,13 +342,15 @@ def short(text):
 return text[:12]
 
 @templatefilter('shortbisect')
-def shortbisect(text):
-"""Any text. Treats `text` as a bisection status, and
+def shortbisect(label):
+"""Any text. Treats `label` as a bisection status, and
 returns a single-character representing the status (G: good, B: bad,
 S: skipped, U: untested, I: ignored). Returns single space if `text`
 is not a valid bisection status.
 """
-return hbisect.shortlabel(text) or ' '
+if label:
+return label[0].upper()
+return ' '
 
 @templatefilter('shortdate')
 def shortdate(text):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 4 of 5] templatefilters: inline hbisect.shortlabel()

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520342101 21600
#  Tue Mar 06 07:15:01 2018 -0600
# Node ID 50008d6c32bf2eba12b13bd2ed34149b653ef004
# Parent  61f2121cac167752acc5014ac7f4c4da100a2097
templatefilters: inline hbisect.shortlabel()

It's pretty simple. I don't think the business logic has to be placed in
hbisect.py.

diff --git a/mercurial/hbisect.py b/mercurial/hbisect.py
--- a/mercurial/hbisect.py
+++ b/mercurial/hbisect.py
@@ -267,12 +267,6 @@ def label(repo, node):
 
 return None
 
-def shortlabel(label):
-if label:
-return label[0].upper()
-
-return None
-
 def printresult(ui, repo, state, displayer, nodes, good):
 if len(nodes) == 1:
 # narrowed it down to a single revision
diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
--- a/mercurial/templatefilters.py
+++ b/mercurial/templatefilters.py
@@ -343,13 +343,15 @@ def short(text):
 return text[:12]
 
 @templatefilter('shortbisect')
-def shortbisect(text):
-"""Any text. Treats `text` as a bisection status, and
+def shortbisect(label):
+"""Any text. Treats `label` as a bisection status, and
 returns a single-character representing the status (G: good, B: bad,
 S: skipped, U: untested, I: ignored). Returns single space if `text`
 is not a valid bisection status.
 """
-return hbisect.shortlabel(text) or ' '
+if label:
+return label[0].upper()
+return ' '
 
 @templatefilter('shortdate')
 def shortdate(text):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 5 of 5] py3: fix slicing of bisect label in templatefilters.shortbisect()

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520342201 21600
#  Tue Mar 06 07:16:41 2018 -0600
# Node ID af33c19550e40f5b13088f1737d9781e07bca548
# Parent  50008d6c32bf2eba12b13bd2ed34149b653ef004
py3: fix slicing of bisect label in templatefilters.shortbisect()

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -18,6 +18,7 @@ test-basic.t
 test-bheads.t
 test-bisect.t
 test-bisect2.t
+test-bisect3.t
 test-blackbox.t
 test-bookmarks-current.t
 test-bookmarks-merge.t
diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
--- a/mercurial/templatefilters.py
+++ b/mercurial/templatefilters.py
@@ -350,7 +350,7 @@ def shortbisect(label):
 is not a valid bisection status.
 """
 if label:
-return label[0].upper()
+return label[0:1].upper()
 return ' '
 
 @templatefilter('shortdate')
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 3 of 5] py3: make test-bisect.t bytes-safe

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520341884 21600
#  Tue Mar 06 07:11:24 2018 -0600
# Node ID 61f2121cac167752acc5014ac7f4c4da100a2097
# Parent  f4f4de07f3d1cd9c9bf3ca94adce78e8b4ae446e
py3: make test-bisect.t bytes-safe

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -16,6 +16,7 @@ test-backout.t
 test-backwards-remove.t
 test-basic.t
 test-bheads.t
+test-bisect.t
 test-bisect2.t
 test-blackbox.t
 test-bookmarks-current.t
diff --git a/tests/test-bisect.t b/tests/test-bisect.t
--- a/tests/test-bisect.t
+++ b/tests/test-bisect.t
@@ -465,8 +465,8 @@ test bisecting command
   > from __future__ import absolute_import
   > import sys
   > from mercurial import hg, ui as uimod
-  > repo = hg.repository(uimod.ui.load(), '.')
-  > if repo['.'].rev() < 6:
+  > repo = hg.repository(uimod.ui.load(), b'.')
+  > if repo[b'.'].rev() < 6:
   > sys.exit(1)
   > EOF
   $ chmod +x script.py
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 2 of 5] py3: fix integer formatting in bisect error

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520341850 21600
#  Tue Mar 06 07:10:50 2018 -0600
# Node ID f4f4de07f3d1cd9c9bf3ca94adce78e8b4ae446e
# Parent  7742cc1155b78a819ee1ecce7783c36bec42548e
py3: fix integer formatting in bisect error

diff --git a/mercurial/hbisect.py b/mercurial/hbisect.py
--- a/mercurial/hbisect.py
+++ b/mercurial/hbisect.py
@@ -55,7 +55,7 @@ def bisect(repo, state):
 if (len(state['bad']) == 1 and len(state['good']) == 1 and
 state['bad'] != state['good']):
 raise error.Abort(_("starting revisions are not directly related"))
-raise error.Abort(_("inconsistent state, %s:%s is good and bad")
+raise error.Abort(_("inconsistent state, %d:%s is good and bad")
  % (badrev, short(bad)))
 
 # build children dict
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 5] py3: silence f.write() in test-annotate.t

2018-03-10 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1520668554 -32400
#  Sat Mar 10 16:55:54 2018 +0900
# Node ID 7742cc1155b78a819ee1ecce7783c36bec42548e
# Parent  a20a3f5171d4eacc6a2f801f0ce39516fb9fd444
py3: silence f.write() in test-annotate.t

diff --git a/tests/test-annotate.t b/tests/test-annotate.t
--- a/tests/test-annotate.t
+++ b/tests/test-annotate.t
@@ -914,10 +914,10 @@ Annotate with orphaned CR (issue5798)
   > EOF
 
   >>> with open('a', 'wb') as f:
-  ... f.write(b'0a\r0b\r\n0c\r0d\r\n0e\n0f\n0g')
+  ... f.write(b'0a\r0b\r\n0c\r0d\r\n0e\n0f\n0g') and None
   $ hg ci -qAm0
   >>> with open('a', 'wb') as f:
-  ... f.write(b'0a\r0b\r\n1c\r1d\r\n0e\n1f\n0g')
+  ... f.write(b'0a\r0b\r\n1c\r1d\r\n0e\n1f\n0g') and None
   $ hg ci -m1
 
   $ hg annotate -r0 a | $PYTHON "$TESTTMP/substcr.py"
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel