hgweb: handle a "linerange" request parameter in filelog command
authorDenis Laxalde <denis.laxalde@logilab.fr>
Thu, 19 Jan 2017 17:41:00 +0100
changeset 31665 5e6d44511317
parent 31664 1cbeefa59343
child 31666 aaebc80c9f1d
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 update the "paper" template in this patch.
mercurial/hgweb/webcommands.py
mercurial/hgweb/webutil.py
mercurial/templates/paper/filelog.tmpl
tests/test-hgweb-filelog.t
--- a/mercurial/hgweb/webcommands.py	Sun Mar 26 16:51:19 2017 -0700
+++ b/mercurial/hgweb/webcommands.py	Thu Jan 19 17:41:00 2017 +0100
@@ -28,6 +28,7 @@
 
 from .. import (
     archival,
+    context,
     encoding,
     error,
     graphmod,
@@ -968,6 +969,8 @@
         except ValueError:
             pass
 
+    lrange = webutil.linerange(req)
+
     lessvars = copy.copy(tmpl.defaults['sessionvars'])
     lessvars['revcount'] = max(revcount / 2, 1)
     morevars = copy.copy(tmpl.defaults['sessionvars'])
@@ -996,24 +999,49 @@
         path = fctx.path()
         return webutil.diffs(web, tmpl, ctx, basectx, [path], diffstyle)
 
-    for i in revs:
-        iterfctx = fctx.filectx(i)
-        diffs = None
-        if patch:
-            diffs = diff(iterfctx)
-        entries.append(dict(
-            parity=next(parity),
-            filerev=i,
-            file=f,
-            diff=diffs,
-            rename=webutil.renamelink(iterfctx),
-            **webutil.commonentry(repo, iterfctx)))
-    entries.reverse()
+    linerange = None
+    if lrange is not None:
+        linerange = webutil.formatlinerange(*lrange)
+        # deactivate numeric nav links when linerange is specified as this
+        # would required a dedicated "revnav" class
+        nav = None
+        ancestors = context.blockancestors(fctx, *lrange)
+        for i, (c, lr) in enumerate(ancestors, 1):
+            diffs = None
+            if patch:
+                diffs = diff(c)
+            # follow renames accross filtered (not in range) revisions
+            path = c.path()
+            entries.append(dict(
+                parity=next(parity),
+                filerev=c.rev(),
+                file=path,
+                diff=diffs,
+                linerange=webutil.formatlinerange(*lr),
+                **webutil.commonentry(repo, c)))
+            if i == revcount:
+                break
+        lessvars['linerange'] = webutil.formatlinerange(*lrange)
+        morevars['linerange'] = lessvars['linerange']
+    else:
+        for i in revs:
+            iterfctx = fctx.filectx(i)
+            diffs = None
+            if patch:
+                diffs = diff(iterfctx)
+            entries.append(dict(
+                parity=next(parity),
+                filerev=i,
+                file=f,
+                diff=diffs,
+                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,
@@ -1021,6 +1049,7 @@
                 entries=entries,
                 patch=patch,
                 latestentry=latestentry,
+                linerange=linerange,
                 revcount=revcount,
                 morevars=morevars,
                 lessvars=lessvars,
--- a/mercurial/hgweb/webutil.py	Sun Mar 26 16:51:19 2017 -0700
+++ b/mercurial/hgweb/webutil.py	Thu Jan 19 17:41:00 2017 +0100
@@ -18,6 +18,7 @@
 
 from .common import (
     ErrorResponse,
+    HTTP_BAD_REQUEST,
     HTTP_NOT_FOUND,
     paritygen,
 )
@@ -317,6 +318,26 @@
 
     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')
+    try:
+        return util.processlinerange(fromline, toline)
+    except error.ParseError as exc:
+        raise ErrorResponse(HTTP_BAD_REQUEST, str(exc))
+
+def formatlinerange(fromline, toline):
+    return '%d:%d' % (fromline + 1, toline)
+
 def commonentry(repo, ctx):
     node = ctx.node()
     return {
--- a/mercurial/templates/paper/filelog.tmpl	Sun Mar 26 16:51:19 2017 -0700
+++ b/mercurial/templates/paper/filelog.tmpl	Thu Jan 19 17:41:00 2017 +0100
@@ -47,6 +47,8 @@
 <h3>
  log {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
  {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+ {if(linerange,
+' (following lines {linerange} <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">back to filelog</a>)')}
 </h3>
 
 <form class="search" action="{url|urlescape}log">
--- a/tests/test-hgweb-filelog.t	Sun Mar 26 16:51:19 2017 -0700
+++ b/tests/test-hgweb-filelog.t	Thu Jan 19 17:41:00 2017 +0100
@@ -190,6 +190,7 @@
   <h3>
    log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
    <span class="branchname">a-branch</span> 
+   
   </h3>
   
   <form class="search" action="/log">
@@ -311,6 +312,7 @@
   <h3>
    log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
    <span class="branchname">a-branch</span> 
+   
   </h3>
   
   <form class="search" action="/log">
@@ -432,6 +434,7 @@
   <h3>
    log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
    <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+   
   </h3>
   
   <form class="search" action="/log">
@@ -544,6 +547,7 @@
   <h3>
    log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
    <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+   
   </h3>
   
   <form class="search" action="/log">
@@ -660,6 +664,264 @@
   
   [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?linerange=1:2')
+  200 Script output follows
+  
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml" xml: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-paper.css" type="text/css" />
+  <script type="text/javascript" src="/static/mercurial.js"></script>
+  
+  <title>test: c history</title>
+  <link rel="alternate" type="application/atom+xml"
+     href="/atom-log/tip/c" title="Atom feed for test:c" />
+  <link rel="alternate" type="application/rss+xml"
+     href="/rss-log/tip/c" title="RSS feed for test:c" />
+  </head>
+  <body>
+  
+  <div class="container">
+  <div class="menu">
+  <div class="logo">
+  <a href="https://mercurial-scm.org/">
+  <img src="/static/hglogo.png" alt="mercurial" /></a>
+  </div>
+  <ul>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
+  <li><a href="/tags">tags</a></li>
+  <li><a href="/bookmarks">bookmarks</a></li>
+  <li><a href="/branches">branches</a></li>
+  </ul>
+  <ul>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
+  </ul>
+  <ul>
+  <li><a href="/file/tip/c">file</a></li>
+  <li><a href="/diff/tip/c">diff</a></li>
+  <li><a href="/comparison/tip/c">comparison</a></li>
+  <li><a href="/annotate/tip/c">annotate</a></li>
+  <li class="active">file log</li>
+  <li><a href="/raw-file/tip/c">raw</a></li>
+  </ul>
+  <ul>
+  <li><a href="/help">help</a></li>
+  </ul>
+  <div class="atom-logo">
+  <a href="/atom-log/tip/c" title="subscribe to atom feed">
+  <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
+  </a>
+  </div>
+  </div>
+  
+  <div class="main">
+  <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
+  <h3>
+   log c @ 7:<a href="/rev/46c1a66bd8fc">46c1a66bd8fc</a>
+   <span class="branchname">a-branch</span> <span class="tag">tip</span> 
+    (following lines 1:2 <a href="/log/tip/c">back to filelog</a>)
+  </h3>
+  
+  <form class="search" action="/log">
+  
+  <p><input name="rev" id="search1" type="text" size="30" /></p>
+  <div id="hint">Find changesets by keywords (author, files, the commit message), revision
+  number or hash, or <a href="/help/revsets">revset expression</a>.</div>
+  </form>
+  
+  <div class="navigate">
+  <a href="/log/tip/c?linerange=1%3A2&revcount=30">less</a>
+  <a href="/log/tip/c?linerange=1%3A2&revcount=120">more</a>
+  |  </div>
+  
+  <table class="bigtable">
+  <thead>
+   <tr>
+    <th class="age">age</th>
+    <th class="author">author</th>
+    <th class="description">description</th>
+   </tr>
+  </thead>
+  <tbody class="stripes2">
+   <tr>
+    <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+    <td class="author">test</td>
+    <td class="description">
+     <a href="/rev/46c1a66bd8fc">change c</a>
+     <span class="branchhead">a-branch</span> <span class="tag">tip</span> 
+    </td>
+   </tr>
+   
+   <tr>
+    <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+    <td class="author">test</td>
+    <td class="description">
+     <a href="/rev/6563da9dcf87">b</a>
+     
+    </td>
+   </tr>
+   
+  
+  </tbody>
+  </table>
+  
+  <div class="navigate">
+  <a href="/log/tip/c?linerange=1%3A2&revcount=30">less</a>
+  <a href="/log/tip/c?linerange=1%3A2&revcount=120">more</a>
+  |  
+  </div>
+  
+  </div>
+  </div>
+  
+  
+  
+  </body>
+  </html>
+  
+  $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?linerange=1%3A2&revcount=1')
+  200 Script output follows
+  
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml" xml: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-paper.css" type="text/css" />
+  <script type="text/javascript" src="/static/mercurial.js"></script>
+  
+  <title>test: c history</title>
+  <link rel="alternate" type="application/atom+xml"
+     href="/atom-log/tip/c" title="Atom feed for test:c" />
+  <link rel="alternate" type="application/rss+xml"
+     href="/rss-log/tip/c" title="RSS feed for test:c" />
+  </head>
+  <body>
+  
+  <div class="container">
+  <div class="menu">
+  <div class="logo">
+  <a href="https://mercurial-scm.org/">
+  <img src="/static/hglogo.png" alt="mercurial" /></a>
+  </div>
+  <ul>
+  <li><a href="/shortlog/tip?revcount=1">log</a></li>
+  <li><a href="/graph/tip?revcount=1">graph</a></li>
+  <li><a href="/tags?revcount=1">tags</a></li>
+  <li><a href="/bookmarks?revcount=1">bookmarks</a></li>
+  <li><a href="/branches?revcount=1">branches</a></li>
+  </ul>
+  <ul>
+  <li><a href="/rev/tip?revcount=1">changeset</a></li>
+  <li><a href="/file/tip?revcount=1">browse</a></li>
+  </ul>
+  <ul>
+  <li><a href="/file/tip/c?revcount=1">file</a></li>
+  <li><a href="/diff/tip/c?revcount=1">diff</a></li>
+  <li><a href="/comparison/tip/c?revcount=1">comparison</a></li>
+  <li><a href="/annotate/tip/c?revcount=1">annotate</a></li>
+  <li class="active">file log</li>
+  <li><a href="/raw-file/tip/c">raw</a></li>
+  </ul>
+  <ul>
+  <li><a href="/help?revcount=1">help</a></li>
+  </ul>
+  <div class="atom-logo">
+  <a href="/atom-log/tip/c" title="subscribe to atom feed">
+  <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
+  </a>
+  </div>
+  </div>
+  
+  <div class="main">
+  <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
+  <h3>
+   log c @ 7:<a href="/rev/46c1a66bd8fc?revcount=1">46c1a66bd8fc</a>
+   <span class="branchname">a-branch</span> <span class="tag">tip</span> 
+    (following lines 1:2 <a href="/log/tip/c?revcount=1">back to filelog</a>)
+  </h3>
+  
+  <form class="search" action="/log">
+  <input type="hidden" name="revcount" value="1" />
+  <p><input name="rev" id="search1" type="text" size="30" /></p>
+  <div id="hint">Find changesets by keywords (author, files, the commit message), revision
+  number or hash, or <a href="/help/revsets">revset expression</a>.</div>
+  </form>
+  
+  <div class="navigate">
+  <a href="/log/tip/c?linerange=1%3A2&revcount=1">less</a>
+  <a href="/log/tip/c?linerange=1%3A2&revcount=2">more</a>
+  |  </div>
+  
+  <table class="bigtable">
+  <thead>
+   <tr>
+    <th class="age">age</th>
+    <th class="author">author</th>
+    <th class="description">description</th>
+   </tr>
+  </thead>
+  <tbody class="stripes2">
+   <tr>
+    <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+    <td class="author">test</td>
+    <td class="description">
+     <a href="/rev/46c1a66bd8fc?revcount=1">change c</a>
+     <span class="branchhead">a-branch</span> <span class="tag">tip</span> 
+    </td>
+   </tr>
+   
+  
+  </tbody>
+  </table>
+  
+  <div class="navigate">
+  <a href="/log/tip/c?linerange=1%3A2&revcount=1">less</a>
+  <a href="/log/tip/c?linerange=1%3A2&revcount=2">more</a>
+  |  
+  </div>
+  
+  </div>
+  </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 fromline 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')
@@ -829,6 +1091,7 @@
   <h3>
    log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
    <span class="branchname">a-branch</span> 
+   
   </h3>
   
   <form class="search" action="/log">