Patchwork [04,of,11] hgweb: handle a "linerange" request parameter in filelog command

login
register
mail settings
Submitter Denis Laxalde
Date Feb. 22, 2017, 3:09 p.m.
Message ID <4d83e067c9c6e0f5fb7b.1487776198@sh77.tls.logilab.fr>
Download mbox | patch
Permalink /patch/18710/
State Accepted
Delegated to: Yuya Nishihara
Headers show

Comments

Denis Laxalde - Feb. 22, 2017, 3:09 p.m.
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1484844060 -3600
#      Thu Jan 19 17:41:00 2017 +0100
# Node ID 4d83e067c9c6e0f5fb7b126a9739aa9dd8113af4
# Parent  0a725b74eeb2a8eca6d1d82ba8ec8c2d188e8684
# EXP-Topic linerange-log/hgweb-filelog
hgweb: handle a "linerange" request parameter in filelog command

We now handle a "linerange" URL query parameter to filter filelog using
a logic similar to followlines() revset.
The URL syntax is: log/<rev>/<file>?linerange=<fromline>-<toline>
As a result, filelog entries only consists of revision changing specified
line range.

The linerange information is propagated to "more"/"less" navigation links but
not to numeric navigation links as this would apparently require a dedicated
"revnav" class.

Only the gitweb template got updated for now.
Yuya Nishihara - Feb. 23, 2017, 2:54 p.m.
On Wed, 22 Feb 2017 16:09:58 +0100, Denis Laxalde wrote:
> # HG changeset patch
> # User Denis Laxalde <denis.laxalde@logilab.fr>
> # Date 1484844060 -3600
> #      Thu Jan 19 17:41:00 2017 +0100
> # Node ID 4d83e067c9c6e0f5fb7b126a9739aa9dd8113af4
> # Parent  0a725b74eeb2a8eca6d1d82ba8ec8c2d188e8684
> # EXP-Topic linerange-log/hgweb-filelog
> hgweb: handle a "linerange" request parameter in filelog command
> 
> We now handle a "linerange" URL query parameter to filter filelog using
> a logic similar to followlines() revset.
> The URL syntax is: log/<rev>/<file>?linerange=<fromline>-<toline>

Nit: how about copying the <fromline>:<toline> syntax from revset?
Yuya Nishihara - Feb. 24, 2017, 3:17 p.m.
On Wed, 22 Feb 2017 16:09:58 +0100, Denis Laxalde wrote:
> # HG changeset patch
> # User Denis Laxalde <denis.laxalde@logilab.fr>
> # Date 1484844060 -3600
> #      Thu Jan 19 17:41:00 2017 +0100
> # Node ID 4d83e067c9c6e0f5fb7b126a9739aa9dd8113af4
> # Parent  0a725b74eeb2a8eca6d1d82ba8ec8c2d188e8684
> # EXP-Topic linerange-log/hgweb-filelog
> hgweb: handle a "linerange" request parameter in filelog command

Looks generally good. A few comments in addition to 'from-to' vs 'from:to'.

> +++ b/mercurial/hgweb/webcommands.py

> @@ -966,6 +967,11 @@ def filelog(web, req, tmpl):
>          except ValueError:
>              pass
>  
> +    lrange = webutil.linerange(req)
> +
> +    def formatlinerange(fromline, toline):
> +        return '%d-%d' % (fromline + 1, toline)

I prefer moving formatlinerange() to webutil since it is considered a paired
function with the linerange().

> +    linerange = None
> +    if lrange is not None:
> +        linerange = formatlinerange(*lrange)
> +        # deactivate numeric nav links when linerange is specified

Can you copy the reason from the commit message? That will help future
readers.

> +        nav = None
> +        ancestors = context.blockancestors(fctx, *lrange, followfirst=True)

I'm not sure if followfirst=True is the right choice since the default filelog
page shows every branch.

> diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py

> +def linerange(req):
> +    linerange = req.form.get('linerange')
> +    if linerange is None:
> +        return None
> +    if len(linerange) > 1:
> +        raise ErrorResponse(HTTP_BAD_REQUEST,
> +                            'redundant linerange parameter')
> +    try:
> +        fromline, toline = map(int, linerange[0].split('-', 1))
> +    except ValueError:
> +        raise ErrorResponse(HTTP_BAD_REQUEST,
> +                            'invalid linerange parameter')

> +    if toline - fromline < 0:
> +        raise ErrorResponse(HTTP_BAD_REQUEST, 'line range must be positive')
> +    if fromline < 1:
> +        raise ErrorResponse(HTTP_BAD_REQUEST,
> +                            'linerange "start" must be strictly positive')
> +    fromline -= 1

This part could be a utility function and shared with revset.followlines().
Gregory Szorc - Feb. 28, 2017, 11:23 p.m.
On Thu, Feb 23, 2017 at 6:54 AM, Yuya Nishihara <yuya@tcha.org> wrote:

> On Wed, 22 Feb 2017 16:09:58 +0100, Denis Laxalde wrote:
> > # HG changeset patch
> > # User Denis Laxalde <denis.laxalde@logilab.fr>
> > # Date 1484844060 -3600
> > #      Thu Jan 19 17:41:00 2017 +0100
> > # Node ID 4d83e067c9c6e0f5fb7b126a9739aa9dd8113af4
> > # Parent  0a725b74eeb2a8eca6d1d82ba8ec8c2d188e8684
> > # EXP-Topic linerange-log/hgweb-filelog
> > hgweb: handle a "linerange" request parameter in filelog command
> >
> > We now handle a "linerange" URL query parameter to filter filelog using
> > a logic similar to followlines() revset.
> > The URL syntax is: log/<rev>/<file>?linerange=<fromline>-<toline>
>
> Nit: how about copying the <fromline>:<toline> syntax from revset?
>

I agree with this suggestion (consistency is good) and see it has been
incorporated into the v2 series. However, I'd like to state for the record
that ':' is a reserved character in URLs and needs to be percent escaped.
So that's slightly annoying, especially for power users that may wish to
tweak URLs in their browser. Although, I'm pretty sure the URL parsers in
browsers will allow the literal ':' so it shouldn't be a big deal in
practice.

Patch

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -28,6 +28,7 @@  from .common import (
 
 from .. import (
     archival,
+    context,
     encoding,
     error,
     graphmod,
@@ -966,6 +967,11 @@  def filelog(web, req, tmpl):
         except ValueError:
             pass
 
+    lrange = webutil.linerange(req)
+
+    def formatlinerange(fromline, toline):
+        return '%d-%d' % (fromline + 1, toline)
+
     lessvars = copy.copy(tmpl.defaults['sessionvars'])
     lessvars['revcount'] = max(revcount / 2, 1)
     morevars = copy.copy(tmpl.defaults['sessionvars'])
@@ -979,26 +985,47 @@  def filelog(web, req, tmpl):
     repo = web.repo
     revs = fctx.filelog().revs(start, end - 1)
     entries = []
-    for i in revs:
-        iterfctx = fctx.filectx(i)
-        entries.append(dict(
-            parity=next(parity),
-            filerev=i,
-            file=f,
-            rename=webutil.renamelink(iterfctx),
-            **webutil.commonentry(repo, iterfctx)))
-    entries.reverse()
+    linerange = None
+    if lrange is not None:
+        linerange = formatlinerange(*lrange)
+        # deactivate numeric nav links when linerange is specified
+        nav = None
+        ancestors = context.blockancestors(fctx, *lrange, followfirst=True)
+        for i, (c, lr) in enumerate(ancestors, 1):
+            # follow renames accross filtered (not in range) revisions
+            path = c.path()
+            entries.append(dict(
+                parity=next(parity),
+                filerev=c.rev(),
+                file=path,
+                linerange=formatlinerange(*lr),
+                **webutil.commonentry(repo, c)))
+            if i == revcount:
+                break
+        lessvars['linerange'] = formatlinerange(*lrange)
+        morevars['linerange'] = lessvars['linerange']
+    else:
+        for i in revs:
+            iterfctx = fctx.filectx(i)
+            entries.append(dict(
+                parity=next(parity),
+                filerev=i,
+                file=f,
+                rename=webutil.renamelink(iterfctx),
+                **webutil.commonentry(repo, iterfctx)))
+        entries.reverse()
+        revnav = webutil.filerevnav(web.repo, fctx.path())
+        nav = revnav.gen(end - 1, revcount, count)
 
     latestentry = entries[:1]
 
-    revnav = webutil.filerevnav(web.repo, fctx.path())
-    nav = revnav.gen(end - 1, revcount, count)
     return tmpl("filelog",
                 file=f,
                 nav=nav,
                 symrev=webutil.symrevorshortnode(req, fctx),
                 entries=entries,
                 latestentry=latestentry,
+                linerange=linerange,
                 revcount=revcount,
                 morevars=morevars,
                 lessvars=lessvars,
diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -18,6 +18,7 @@  from ..node import hex, nullid, short
 
 from .common import (
     ErrorResponse,
+    HTTP_BAD_REQUEST,
     HTTP_NOT_FOUND,
     paritygen,
 )
@@ -313,6 +314,26 @@  def filectx(repo, req):
 
     return fctx
 
+def linerange(req):
+    linerange = req.form.get('linerange')
+    if linerange is None:
+        return None
+    if len(linerange) > 1:
+        raise ErrorResponse(HTTP_BAD_REQUEST,
+                            'redundant linerange parameter')
+    try:
+        fromline, toline = map(int, linerange[0].split('-', 1))
+    except ValueError:
+        raise ErrorResponse(HTTP_BAD_REQUEST,
+                            'invalid linerange parameter')
+    if toline - fromline < 0:
+        raise ErrorResponse(HTTP_BAD_REQUEST, 'line range must be positive')
+    if fromline < 1:
+        raise ErrorResponse(HTTP_BAD_REQUEST,
+                            'linerange "start" must be strictly positive')
+    fromline -= 1
+    return fromline, toline
+
 def commonentry(repo, ctx):
     node = ctx.node()
     return {
diff --git a/mercurial/templates/gitweb/filelog.tmpl b/mercurial/templates/gitweb/filelog.tmpl
--- a/mercurial/templates/gitweb/filelog.tmpl
+++ b/mercurial/templates/gitweb/filelog.tmpl
@@ -31,7 +31,9 @@  revisions |
 {nav%filenav}
 </div>
 
-<div class="title" >{file|urlescape}</div>
+<div class="title">{file|urlescape}{if(linerange,
+' (following lines {linerange}  <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">back to filelog</a>)'
+)}</div>
 
 <table>
 {entries%filelogentry}
diff --git a/tests/test-hgweb-filelog.t b/tests/test-hgweb-filelog.t
--- a/tests/test-hgweb-filelog.t
+++ b/tests/test-hgweb-filelog.t
@@ -654,6 +654,219 @@  before addition - error
   
   [1]
 
+  $ hg log -r 'followlines(c, 1:2, startrev=tip) and follow(c)'
+  changeset:   0:6563da9dcf87
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     b
+  
+  changeset:   7:46c1a66bd8fc
+  branch:      a-branch
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     change c
+  
+  $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?style=gitweb&linerange=1-2')
+  200 Script output follows
+  
+  <?xml version="1.0" encoding="ascii"?>
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+  <head>
+  <link rel="icon" href="/static/hgicon.png" type="image/png" />
+  <meta name="robots" content="index, nofollow"/>
+  <link rel="stylesheet" href="/static/style-gitweb.css" type="text/css" />
+  <script type="text/javascript" src="/static/mercurial.js"></script>
+  
+  <title>test: File revisions</title>
+  <link rel="alternate" type="application/atom+xml"
+     href="/atom-log" title="Atom feed for test"/>
+  <link rel="alternate" type="application/rss+xml"
+     href="/rss-log" title="RSS feed for test"/>
+  </head>
+  <body>
+  
+  <div class="page_header">
+  <a href="https://mercurial-scm.org/" title="Mercurial" style="float: right;">Mercurial</a>
+  <a href="/">Mercurial</a>  / file revisions
+  </div>
+  
+  <div class="page_nav">
+  <a href="/summary?style=gitweb">summary</a> |
+  <a href="/shortlog?style=gitweb">shortlog</a> |
+  <a href="/log?style=gitweb">changelog</a> |
+  <a href="/graph?style=gitweb">graph</a> |
+  <a href="/tags?style=gitweb">tags</a> |
+  <a href="/bookmarks?style=gitweb">bookmarks</a> |
+  <a href="/branches?style=gitweb">branches</a> |
+  <a href="/file/tip/c?style=gitweb">file</a> |
+  revisions |
+  <a href="/annotate/tip/c?style=gitweb">annotate</a> |
+  <a href="/diff/tip/c?style=gitweb">diff</a> |
+  <a href="/comparison/tip/c?style=gitweb">comparison</a> |
+  <a href="/rss-log/tip/c">rss</a> |
+  <a href="/help?style=gitweb">help</a>
+  <br/>
+   
+  </div>
+  
+  <div class="title">c (following lines 1-2  <a href="/log/tip/c?style=gitweb">back to filelog</a>)</div>
+  
+  <table>
+  
+  <tr class="parity1">
+  <td class="age"><i class="age">Thu, 01 Jan 1970 00:00:00 +0000</i></td>
+  <td><i>test</i></td>
+  <td>
+  <a class="list" href="/rev/46c1a66bd8fc?style=gitweb">
+  <b>change c</b>
+  <span class="logtags"><span class="branchtag" title="a-branch">a-branch</span> <span class="tagtag" title="tip">tip</span> </span>
+  </a>
+  </td>
+  <td class="link">
+  <a href="/file/46c1a66bd8fc/c?style=gitweb">file</a> |
+  <a href="/diff/46c1a66bd8fc/c?style=gitweb">diff</a> |
+  <a href="/annotate/46c1a66bd8fc/c?style=gitweb">annotate</a>
+  
+  </td>
+  </tr>
+  <tr class="parity0">
+  <td class="age"><i class="age">Thu, 01 Jan 1970 00:00:00 +0000</i></td>
+  <td><i>test</i></td>
+  <td>
+  <a class="list" href="/rev/6563da9dcf87?style=gitweb">
+  <b>b</b>
+  <span class="logtags"></span>
+  </a>
+  </td>
+  <td class="link">
+  <a href="/file/6563da9dcf87/b?style=gitweb">file</a> |
+  <a href="/diff/6563da9dcf87/b?style=gitweb">diff</a> |
+  <a href="/annotate/6563da9dcf87/b?style=gitweb">annotate</a>
+  
+  </td>
+  </tr>
+  </table>
+  
+  <div class="page_nav">
+  <a href="/log/tip/c?linerange=1-2&revcount=30&style=gitweb">less</a>
+  <a href="/log/tip/c?linerange=1-2&revcount=120&style=gitweb">more</a>
+   
+  </div>
+  
+  <div class="page_footer">
+  <div class="page_footer_text">test</div>
+  <div class="rss_logo">
+  <a href="/rss-log">RSS</a>
+  <a href="/atom-log">Atom</a>
+  </div>
+  <br />
+  
+  </div>
+  </body>
+  </html>
+  
+  $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?style=gitweb&linerange=1-2&revcount=1')
+  200 Script output follows
+  
+  <?xml version="1.0" encoding="ascii"?>
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+  <head>
+  <link rel="icon" href="/static/hgicon.png" type="image/png" />
+  <meta name="robots" content="index, nofollow"/>
+  <link rel="stylesheet" href="/static/style-gitweb.css" type="text/css" />
+  <script type="text/javascript" src="/static/mercurial.js"></script>
+  
+  <title>test: File revisions</title>
+  <link rel="alternate" type="application/atom+xml"
+     href="/atom-log" title="Atom feed for test"/>
+  <link rel="alternate" type="application/rss+xml"
+     href="/rss-log" title="RSS feed for test"/>
+  </head>
+  <body>
+  
+  <div class="page_header">
+  <a href="https://mercurial-scm.org/" title="Mercurial" style="float: right;">Mercurial</a>
+  <a href="/">Mercurial</a>  / file revisions
+  </div>
+  
+  <div class="page_nav">
+  <a href="/summary?revcount=1&style=gitweb">summary</a> |
+  <a href="/shortlog?revcount=1&style=gitweb">shortlog</a> |
+  <a href="/log?revcount=1&style=gitweb">changelog</a> |
+  <a href="/graph?revcount=1&style=gitweb">graph</a> |
+  <a href="/tags?revcount=1&style=gitweb">tags</a> |
+  <a href="/bookmarks?revcount=1&style=gitweb">bookmarks</a> |
+  <a href="/branches?revcount=1&style=gitweb">branches</a> |
+  <a href="/file/tip/c?revcount=1&style=gitweb">file</a> |
+  revisions |
+  <a href="/annotate/tip/c?revcount=1&style=gitweb">annotate</a> |
+  <a href="/diff/tip/c?revcount=1&style=gitweb">diff</a> |
+  <a href="/comparison/tip/c?revcount=1&style=gitweb">comparison</a> |
+  <a href="/rss-log/tip/c">rss</a> |
+  <a href="/help?revcount=1&style=gitweb">help</a>
+  <br/>
+   
+  </div>
+  
+  <div class="title">c (following lines 1-2  <a href="/log/tip/c?revcount=1&style=gitweb">back to filelog</a>)</div>
+  
+  <table>
+  
+  <tr class="parity0">
+  <td class="age"><i class="age">Thu, 01 Jan 1970 00:00:00 +0000</i></td>
+  <td><i>test</i></td>
+  <td>
+  <a class="list" href="/rev/46c1a66bd8fc?revcount=1&style=gitweb">
+  <b>change c</b>
+  <span class="logtags"><span class="branchtag" title="a-branch">a-branch</span> <span class="tagtag" title="tip">tip</span> </span>
+  </a>
+  </td>
+  <td class="link">
+  <a href="/file/46c1a66bd8fc/c?revcount=1&style=gitweb">file</a> |
+  <a href="/diff/46c1a66bd8fc/c?revcount=1&style=gitweb">diff</a> |
+  <a href="/annotate/46c1a66bd8fc/c?revcount=1&style=gitweb">annotate</a>
+  
+  </td>
+  </tr>
+  </table>
+  
+  <div class="page_nav">
+  <a href="/log/tip/c?linerange=1-2&revcount=1&style=gitweb">less</a>
+  <a href="/log/tip/c?linerange=1-2&revcount=2&style=gitweb">more</a>
+   
+  </div>
+  
+  <div class="page_footer">
+  <div class="page_footer_text">test</div>
+  <div class="rss_logo">
+  <a href="/rss-log">RSS</a>
+  <a href="/atom-log">Atom</a>
+  </div>
+  <br />
+  
+  </div>
+  </body>
+  </html>
+  
+  $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=1' --headeronly)
+  400 invalid linerange parameter
+  [1]
+  $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=1-a' --headeronly)
+  400 invalid linerange parameter
+  [1]
+  $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=1-2&linerange=3-4' --headeronly)
+  400 redundant linerange parameter
+  [1]
+  $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=3-2' --headeronly)
+  400 line range must be positive
+  [1]
+  $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=0-1' --headeronly)
+  400 linerange "start" must be strictly positive
+  [1]
+
 should show base link, use spartan because it shows it
 
   $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?style=spartan')