Patchwork [8,of,8,V2] hgweb: add a "followlines" link to hover-box in annotate

login
register
mail settings
Submitter Denis Laxalde
Date Feb. 25, 2017, 9:06 a.m.
Message ID <f1d1d7bcba3cd8678711.1488013565@marimba>
Download mbox | patch
Permalink /patch/18773/
State Changes Requested
Headers show

Comments

Denis Laxalde - Feb. 25, 2017, 9:06 a.m.
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1487773255 -3600
#      Wed Feb 22 15:20:55 2017 +0100
# Node ID f1d1d7bcba3cd86787117cd44616ad1a5c619c5e
# Parent  073bd73b65afcb2acd4783c14cb2983ef21fb5a6
# Available At https://hg.logilab.org/users/dlaxalde/hg
#              hg pull https://hg.logilab.org/users/dlaxalde/hg -r f1d1d7bcba3c
# EXP-Topic linerange-log/hgweb-filelog
hgweb: add a "followlines" link to hover-box in annotate

The hover-box on line numbers in annotate view now holds a "follow lines
<fromline>:<toline>" link where '<fromline>:<toline>' corresponds to the range
of line numbers annotated by a given revision. This link points to the filelog
view where the log is filtered to only include revisions changing the line
range at stake (through `?linerange=<fromline>:<toline>` query parameter).
From this view, one can follow "diff" links which also points to filediff view
with only selected line range visible.
Gregory Szorc - March 1, 2017, midnight
On Sat, Feb 25, 2017 at 1:06 AM, Denis Laxalde <denis@laxalde.org> wrote:

> # HG changeset patch
> # User Denis Laxalde <denis.laxalde@logilab.fr>
> # Date 1487773255 -3600
> #      Wed Feb 22 15:20:55 2017 +0100
> # Node ID f1d1d7bcba3cd86787117cd44616ad1a5c619c5e
> # Parent  073bd73b65afcb2acd4783c14cb2983ef21fb5a6
> # Available At https://hg.logilab.org/users/dlaxalde/hg
> #              hg pull https://hg.logilab.org/users/dlaxalde/hg -r
> f1d1d7bcba3c
> # EXP-Topic linerange-log/hgweb-filelog
> hgweb: add a "followlines" link to hover-box in annotate
>
> The hover-box on line numbers in annotate view now holds a "follow lines
> <fromline>:<toline>" link where '<fromline>:<toline>' corresponds to the
> range
> of line numbers annotated by a given revision. This link points to the
> filelog
> view where the log is filtered to only include revisions changing the line
> range at stake (through `?linerange=<fromline>:<toline>` query parameter).
> From this view, one can follow "diff" links which also points to filediff
> view
> with only selected line range visible.
>

I'm going to gently push back and question the utility of this feature.
First, obviously there are some killer features for looking at history of a
range of lines. But I'm questioning whether this specific implementation
adds enough value.

The annotate hover box as implemented in this patch links to a page showing
a list of revisions touching those lines. That's useful. But I argue it is
less useful than the following alternatives:

* Show history for just this line
* Show history for this function
* Show history for a range of lines I specify

The reason is (and this could just be my own experience biasing me) that
the annotate block rarely maps to the unit being investigated when doing
code archeology. When I'm looking at the annotate history of a file, I'm
usually investigating a specific line, function, or range of lines. The
annotate blocks don't map cleanly to those because in well-established
files with lots of history, the annotate blocks are effectively random
noise in terms of lines they cover. So with this implementation, there are
many scenarios where the line range block is too small or too wide.

I suppose looking at the line history of an annotate block is better than
nothing and you have to start somewhere. But there are far more useful
implementations IMO.

Also, the page it links to showing the revisions with links to a line range
filtered diff is good but not great. I imagine developers will complain
that it requires too many clicks to go to what they want (the actual line
content). IMO it would be *really* useful if there were a single page
showing multiple changesets and line range data for either/and a) raw line
content b) annotate view c) diff view. That way, developers could scroll
and see all versions of that line range. If you were looking at say the
history of a function, this would be *extremely* useful compared to loading
N pages. This improvement could be done as a follow-up and doesn't need to
block this series.

Again, this is great work and this is going to be a great feature. When
this is complete, I anticipate the feature being stolen by Bitbucket,
GitLab, GitHub, and others, as it is really a killer feature for code
archeology.


>
> diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.
> py
> --- a/mercurial/hgweb/webcommands.py
> +++ b/mercurial/hgweb/webcommands.py
> @@ -902,6 +902,14 @@ def annotate(web, req, tmpl):
>          for p in parentscache[rev]:
>              yield p
>
> +    def addlinerange(lines):
> +        if lines:
> +            linerange = webutil.formatlinerange(
> +                lines[0]['lineno'] - 1, lines[-1]['lineno'])
> +            for l in lines:
> +                l['linerange'] = linerange
> +                yield l
> +
>      def annotate(**map):
>          if util.binary(fctx.data()):
>              mt = (mimetypes.guess_type(fctx.path())[0]
> @@ -917,7 +925,7 @@ def annotate(web, req, tmpl):
>              rev = f.rev()
>              if rev != previousrev:
>                  # new block, yield lines from previous one
> -                for linectx in block:
> +                for linectx in addlinerange(block):
>                      yield linectx
>                  block = []
>                  blockhead = True
> @@ -943,7 +951,7 @@ def annotate(web, req, tmpl):
>                  "linenumber": "% 6d" % (lineno + 1),
>                  "revdate": f.date(),
>              })
> -        for linectx in block:
> +        for linectx in addlinerange(block):
>              yield linectx
>
>      return tmpl("fileannotate",
> diff --git a/mercurial/templates/gitweb/map b/mercurial/templates/gitweb/
> map
> --- a/mercurial/templates/gitweb/map
> +++ b/mercurial/templates/gitweb/map
> @@ -111,6 +111,9 @@ annotateline = '
>          <div>parents: {parents%annotateparent}</div>
>          <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{
> sessionvars%urlparameter}">diff</a>
>          <a href="{url|urlescape}rev/{node|short}{sessionvars%
> urlparameter}">changeset</a>
> +        <div>
> +          follow lines <a href="{url|urlescape}log/{
> node|short}/{file|urlescape}{sessionvars%urlparameter}{if(sessionvars,
> "&", "?")}linerange={linerange}">{linerange}</a>
> +        </div>
>        </div>
>      </td>
>      <td><pre><a class="linenr" href="#{lineid}">{linenumber}<
> /a></pre></td>
> diff --git a/mercurial/templates/paper/map b/mercurial/templates/paper/map
> --- a/mercurial/templates/paper/map
> +++ b/mercurial/templates/paper/map
> @@ -92,6 +92,9 @@ annotateline = '
>          <div>parents: {parents%annotateparent}</div>
>          <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{
> sessionvars%urlparameter}">diff</a>
>          <a href="{url|urlescape}rev/{node|short}{sessionvars%
> urlparameter}">changeset</a>
> +        <div>
> +          follow lines <a href="{url|urlescape}log/{
> node|short}/{file|urlescape}{sessionvars%urlparameter}{if(sessionvars,
> '&', '?')}linerange={linerange}">{linerange}</a>
> +        </div>
>        </div>
>      </td>
>      <td class="source"><a href="#{lineid}">{linenumber}</a>
> {line|escape}</td>
> diff --git a/tests/test-hgweb-symrev.t b/tests/test-hgweb-symrev.t
> --- a/tests/test-hgweb-symrev.t
> +++ b/tests/test-hgweb-symrev.t
> @@ -194,11 +194,13 @@ Set up the repo
>    <a href="/annotate/43c799df6e75/foo?style=paper#l1">
>    <a href="/diff/43c799df6e75/foo?style=paper">diff</a>
>    <a href="/rev/43c799df6e75?style=paper">changeset</a>
> +  follow lines <a href="/log/43c799df6e75/foo?
> style=paper&linerange=1:1">1:1</a>
>    <a href="/annotate/a7c1559b7bba/foo?style=paper#l2">
>    <a href="/annotate/a7c1559b7bba/foo?style=paper#l2">
>    <a href="/annotate/43c799df6e75/foo?style=paper">0</a></div>
>    <a href="/diff/a7c1559b7bba/foo?style=paper">diff</a>
>    <a href="/rev/a7c1559b7bba?style=paper">changeset</a>
> +  follow lines <a href="/log/a7c1559b7bba/foo?
> style=paper&linerange=2:2">2:2</a>
>
>    $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT
> 'diff/xyzzy/foo?style=paper' | egrep $REVLINKS
>    <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
> @@ -389,11 +391,13 @@ Set up the repo
>    <a href="/annotate/43c799df6e75/foo?style=coal#l1">
>    <a href="/diff/43c799df6e75/foo?style=coal">diff</a>
>    <a href="/rev/43c799df6e75?style=coal">changeset</a>
> +  follow lines <a href="/log/43c799df6e75/foo?
> style=coal&linerange=1:1">1:1</a>
>    <a href="/annotate/a7c1559b7bba/foo?style=coal#l2">
>    <a href="/annotate/a7c1559b7bba/foo?style=coal#l2">
>    <a href="/annotate/43c799df6e75/foo?style=coal">0</a></div>
>    <a href="/diff/a7c1559b7bba/foo?style=coal">diff</a>
>    <a href="/rev/a7c1559b7bba?style=coal">changeset</a>
> +  follow lines <a href="/log/a7c1559b7bba/foo?
> style=coal&linerange=2:2">2:2</a>
>
>    $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT
> 'diff/xyzzy/foo?style=coal' | egrep $REVLINKS
>    <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
> @@ -636,11 +640,13 @@ Set up the repo
>    <a href="/annotate/43c799df6e75/foo?style=gitweb#l1">
>    <a href="/diff/43c799df6e75/foo?style=gitweb">diff</a>
>    <a href="/rev/43c799df6e75?style=gitweb">changeset</a>
> +  follow lines <a href="/log/43c799df6e75/foo?
> style=gitweb&linerange=1:1">1:1</a>
>    <a href="/annotate/a7c1559b7bba/foo?style=gitweb#l2">
>    <a href="/annotate/a7c1559b7bba/foo?style=gitweb#l2">
>    <a href="/annotate/43c799df6e75/foo?style=gitweb">0</a></div>
>    <a href="/diff/a7c1559b7bba/foo?style=gitweb">diff</a>
>    <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a>
> +  follow lines <a href="/log/a7c1559b7bba/foo?
> style=gitweb&linerange=2:2">2:2</a>
>
>    $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT
> 'diff/xyzzy/foo?style=gitweb' | egrep $REVLINKS
>    <a href="/file/xyzzy?style=gitweb">files</a> |
> diff --git a/tests/test-highlight.t b/tests/test-highlight.t
> --- a/tests/test-highlight.t
> +++ b/tests/test-highlight.t
> @@ -303,6 +303,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l1">     1</a> <span
> class="c">#!/usr/bin/env python</span></td>
> @@ -320,6 +323,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l2">     2</a> </td>
> @@ -337,6 +343,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l3">     3</a> <span
> class="sd">&quot;&quot;&quot;Fun with generators. Corresponding Haskell
> implementation:</span></td>
> @@ -354,6 +363,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l4">     4</a> </td>
> @@ -371,6 +383,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l5">     5</a> <span class="sd">primes = 2
> : sieve [3, 5..]</span></td>
> @@ -388,6 +403,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l6">     6</a> <span class="sd">    where
> sieve (p:ns) = p : sieve [n | n &lt;- ns, mod n p /= 0]</span></td>
> @@ -405,6 +423,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l7">     7</a> <span
> class="sd">&quot;&quot;&quot;</span></td>
> @@ -422,6 +443,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l8">     8</a> </td>
> @@ -439,6 +463,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l9">     9</a> <span
> class="kn">from</span> <span class="nn">itertools</span> <span
> class="kn">import</span> <span class="n">dropwhile</span><span
> class="p">,</span> <span class="n">ifilter</span><span class="p">,</span>
> <span class="n">islice</span><span class="p">,</span> <span
> class="n">count</span><span class="p">,</span> <span
> class="n">chain</span></td>
> @@ -456,6 +483,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l10">    10</a> </td>
> @@ -473,6 +503,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l11">    11</a> <span
> class="kn">def</span> <span class="nf">primes</span><span
> class="p">():</span></td>
> @@ -490,6 +523,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l12">    12</a>     <span
> class="sd">&quot;&quot;&quot;Generate all primes.&quot;&quot;&quot;</
> span></td>
> @@ -507,6 +543,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l13">    13</a>     <span
> class="kn">def</span> <span class="nf">sieve</span><span
> class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
> @@ -524,6 +563,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l14">    14</a>         <span
> class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span
> class="o">.</span><span class="n">next</span><span class="p">()</span></td>
> @@ -541,6 +583,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l15">    15</a>         <span class="c">#
> It is important to yield *here* in order to stop the</span></td>
> @@ -558,6 +603,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l16">    16</a>         <span class="c">#
> infinite recursion.</span></td>
> @@ -575,6 +623,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l17">    17</a>         <span
> class="kn">yield</span> <span class="n">p</span></td>
> @@ -592,6 +643,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l18">    18</a>         <span
> class="n">ns</span> <span class="o">=</span> <span
> class="n">ifilter</span><span class="p">(</span><span
> class="kn">lambda</span> <span class="n">n</span><span class="p">:</span>
> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span>
> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span>
> <span class="n">ns</span><span class="p">)</span></td>
> @@ -609,6 +663,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l19">    19</a>         <span
> class="kn">for</span> <span class="n">n</span> <span class="ow">in</span>
> <span class="n">sieve</span><span class="p">(</span><span
> class="n">ns</span><span class="p">):</span></td>
> @@ -626,6 +683,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l20">    20</a>             <span
> class="kn">yield</span> <span class="n">n</span></td>
> @@ -643,6 +703,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l21">    21</a> </td>
> @@ -660,6 +723,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l22">    22</a>     <span
> class="n">odds</span> <span class="o">=</span> <span
> class="n">ifilter</span><span class="p">(</span><span
> class="kn">lambda</span> <span class="n">i</span><span class="p">:</span>
> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span>
> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span>
> <span class="n">count</span><span class="p">())</span></td>
> @@ -677,6 +743,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l23">    23</a>     <span
> class="kn">return</span> <span class="n">chain</span><span
> class="p">([</span><span class="mi">2</span><span class="p">],</span> <span
> class="n">sieve</span><span class="p">(</span><span
> class="n">dropwhile</span><span class="p">(</span><span
> class="kn">lambda</span> <span class="n">n</span><span class="p">:</span>
> <span class="n">n</span> <span class="o">&lt;</span> <span
> class="mi">3</span><span class="p">,</span> <span
> class="n">odds</span><span class="p">)))</span></td>
> @@ -694,6 +763,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l24">    24</a> </td>
> @@ -711,6 +783,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l25">    25</a> <span class="kn">if</span>
> <span class="n">__name__</span> <span class="o">==</span> <span
> class="s">&quot;__main__&quot;</span><span class="p">:</span></td>
> @@ -728,6 +803,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l26">    26</a>     <span
> class="kn">import</span> <span class="nn">sys</span></td>
> @@ -745,6 +823,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l27">    27</a>     <span
> class="kn">try</span><span class="p">:</span></td>
> @@ -762,6 +843,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l28">    28</a>         <span
> class="n">n</span> <span class="o">=</span> <span
> class="nb">int</span><span class="p">(</span><span
> class="n">sys</span><span class="o">.</span><span
> class="n">argv</span><span class="p">[</span><span class="mi">1</span><span
> class="p">])</span></td>
> @@ -779,6 +863,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l29">    29</a>     <span
> class="kn">except</span> <span class="p">(</span><span
> class="ne">ValueError</span><span class="p">,</span> <span
> class="ne">IndexError</span><span class="p">):</span></td>
> @@ -796,6 +883,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l30">    30</a>         <span
> class="n">n</span> <span class="o">=</span> <span class="mi">10</span></td>
> @@ -813,6 +903,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l31">    31</a>     <span
> class="n">p</span> <span class="o">=</span> <span
> class="n">primes</span><span class="p">()</span></td>
> @@ -830,6 +923,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l32">    32</a>     <span
> class="kn">print</span> <span class="s">&quot;The first </span><span
> class="si">%d</span><span class="s"> primes: </span><span
> class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span>
> <span class="p">(</span><span class="n">n</span><span class="p">,</span>
> <span class="nb">list</span><span class="p">(</span><span
> class="n">islice</span><span class="p">(</span><span
> class="n">p</span><span class="p">,</span> <span class="n">n</span><span
> class="p">)))</span></td>
> @@ -847,6 +943,9 @@ hgweb fileannotate, html
>    <div>parents: </div>
>    <a href="/diff/06824edf55d0/primes.py">diff</a>
>    <a href="/rev/06824edf55d0">changeset</a>
> +  <div>
> +  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:
> 33</a>
> +  </div>
>    </div>
>    </td>
>    <td class="source"><a href="#l33">    33</a> </td>
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
>
Denis Laxalde - March 1, 2017, 10:52 a.m.
Gregory Szorc a écrit :
> On Sat, Feb 25, 2017 at 1:06 AM, Denis Laxalde <denis@laxalde.org> wrote:
>
>> # HG changeset patch
>> # User Denis Laxalde <denis.laxalde@logilab.fr>
>> # Date 1487773255 -3600
>> #      Wed Feb 22 15:20:55 2017 +0100
>> # Node ID f1d1d7bcba3cd86787117cd44616ad1a5c619c5e
>> # Parent  073bd73b65afcb2acd4783c14cb2983ef21fb5a6
>> # Available At https://hg.logilab.org/users/dlaxalde/hg
>> #              hg pull https://hg.logilab.org/users/dlaxalde/hg -r
>> f1d1d7bcba3c
>> # EXP-Topic linerange-log/hgweb-filelog
>> hgweb: add a "followlines" link to hover-box in annotate
>>
>> The hover-box on line numbers in annotate view now holds a "follow lines
>> <fromline>:<toline>" link where '<fromline>:<toline>' corresponds to the
>> range
>> of line numbers annotated by a given revision. This link points to the
>> filelog
>> view where the log is filtered to only include revisions changing the line
>> range at stake (through `?linerange=<fromline>:<toline>` query parameter).
>> From this view, one can follow "diff" links which also points to filediff
>> view
>> with only selected line range visible.
>>
> I'm going to gently push back and question the utility of this feature.
> First, obviously there are some killer features for looking at history of a
> range of lines. But I'm questioning whether this specific implementation
> adds enough value.

I'm glad you do this since I wasn't myself much convinced of the added
value of this feature. This is just the simplest way of exposing the
"URL-based API" introduced in previous patches that I could think of at
the moment and I did not want to invest more in something fancier before
having discussions.
(Basically "forgot" the trailing (RFC) in commit message's first line.)

>
> The annotate hover box as implemented in this patch links to a page showing
> a list of revisions touching those lines. That's useful. But I argue it is
> less useful than the following alternatives:
>
> * Show history for just this line
> * Show history for this function
> * Show history for a range of lines I specify
>
> The reason is (and this could just be my own experience biasing me) that
> the annotate block rarely maps to the unit being investigated when doing
> code archeology. When I'm looking at the annotate history of a file, I'm
> usually investigating a specific line, function, or range of lines. The
> annotate blocks don't map cleanly to those because in well-established
> files with lots of history, the annotate blocks are effectively random
> noise in terms of lines they cover. So with this implementation, there are
> many scenarios where the line range block is too small or too wide.
>
> I suppose looking at the line history of an annotate block is better than
> nothing and you have to start somewhere. But there are far more useful
> implementations IMO.

I totally agree.

I think one way to achieve a more interesting exposure of this feature
would be to start from /file/<rev>/<filepath> view and have a way to
select a range of lines.
One simple thing would be to implement form with two <select> elements
in the header of the view. This my second choice for a "quickstart"
implementation because I though having a form on top of the page would
require too much scrolling and line-counting.
One fancier idea would be take advantage of highlighting with pygments
to let the user select the range of lines based on language tokens (not
sure how big this task would be though).

> Also, the page it links to showing the revisions with links to a line range
> filtered diff is good but not great. I imagine developers will complain
> that it requires too many clicks to go to what they want (the actual line
> content). IMO it would be *really* useful if there were a single page
> showing multiple changesets and line range data for either/and a) raw line
> content b) annotate view c) diff view. That way, developers could scroll
> and see all versions of that line range. If you were looking at say the
> history of a function, this would be *extremely* useful compared to loading
> N pages. This improvement could be done as a follow-up and doesn't need to
> block this series.

I can work on a ?patch=1 URL API (mimicking `hg log -p`) that would
indeed be useful in this context.

>
> Again, this is great work and this is going to be a great feature. When
> this is complete, I anticipate the feature being stolen by Bitbucket,
> GitLab, GitHub, and others, as it is really a killer feature for code
> archeology.
>

Thanks :)
Yuya Nishihara - March 1, 2017, 2:52 p.m.
On Tue, 28 Feb 2017 16:00:44 -0800, Gregory Szorc wrote:
> Also, the page it links to showing the revisions with links to a line range
> filtered diff is good but not great. I imagine developers will complain
> that it requires too many clicks to go to what they want (the actual line
> content). IMO it would be *really* useful if there were a single page
> showing multiple changesets and line range data for either/and a) raw line
> content b) annotate view c) diff view. That way, developers could scroll
> and see all versions of that line range. If you were looking at say the
> history of a function, this would be *extremely* useful compared to loading
> N pages. This improvement could be done as a follow-up and doesn't need to
> block this series.

When the page gets more dynamically rendered, will we still need the mdiff
filtering function? I guess filtering will be handled by js, and we'll need
to label all chunks instead of filtering them on server side.

I think the mdiff and patch.diff() APIs are the most controversial part in
this series. If we can go without them, that would be nice.
Denis Laxalde - March 1, 2017, 4:36 p.m.
Yuya Nishihara a écrit :
> On Tue, 28 Feb 2017 16:00:44 -0800, Gregory Szorc wrote:
>> Also, the page it links to showing the revisions with links to a line range
>> filtered diff is good but not great. I imagine developers will complain
>> that it requires too many clicks to go to what they want (the actual line
>> content). IMO it would be *really* useful if there were a single page
>> showing multiple changesets and line range data for either/and a) raw line
>> content b) annotate view c) diff view. That way, developers could scroll
>> and see all versions of that line range. If you were looking at say the
>> history of a function, this would be *extremely* useful compared to loading
>> N pages. This improvement could be done as a follow-up and doesn't need to
>> block this series.
>
> When the page gets more dynamically rendered, will we still need the mdiff
> filtering function? I guess filtering will be handled by js, and we'll need
> to label all chunks instead of filtering them on server side.
>
> I think the mdiff and patch.diff() APIs are the most controversial part in
> this series. If we can go without them, that would be nice.
>

Maybe filtering can occur in webutil.diffs only, but I seems to me that
this would imply parsing diff headers (@@ +start,count -start,count @@).
I think that filtering in mdiff/patch.diff is cleaner but I agree that
the API is not nice. Also, working on mdiff/patch.diff side will
probably be necessary when implementing the command-line version of this
feature.
Yuya Nishihara - March 2, 2017, 3:33 p.m.
On Wed, 1 Mar 2017 17:36:48 +0100, Denis Laxalde wrote:
> Yuya Nishihara a écrit :
> > On Tue, 28 Feb 2017 16:00:44 -0800, Gregory Szorc wrote:
> >> Also, the page it links to showing the revisions with links to a line range
> >> filtered diff is good but not great. I imagine developers will complain
> >> that it requires too many clicks to go to what they want (the actual line
> >> content). IMO it would be *really* useful if there were a single page
> >> showing multiple changesets and line range data for either/and a) raw line
> >> content b) annotate view c) diff view. That way, developers could scroll
> >> and see all versions of that line range. If you were looking at say the
> >> history of a function, this would be *extremely* useful compared to loading
> >> N pages. This improvement could be done as a follow-up and doesn't need to
> >> block this series.
> >
> > When the page gets more dynamically rendered, will we still need the mdiff
> > filtering function? I guess filtering will be handled by js, and we'll need
> > to label all chunks instead of filtering them on server side.
> >
> > I think the mdiff and patch.diff() APIs are the most controversial part in
> > this series. If we can go without them, that would be nice.
> >
> 
> Maybe filtering can occur in webutil.diffs only, but I seems to me that
> this would imply parsing diff headers (@@ +start,count -start,count @@).
> I think that filtering in mdiff/patch.diff is cleaner but I agree that
> the API is not nice. Also, working on mdiff/patch.diff side will
> probably be necessary when implementing the command-line version of this
> feature.

One idea is to attach a context to each chunk so we can filter them later or
embed in <span data-xxx=""> attribute. For example,

  patch.diff() yields text
  patch.diffchunks() yields (text, some_line_information)
Denis Laxalde - March 6, 2017, 9:32 a.m.
Yuya Nishihara a écrit :
> On Wed, 1 Mar 2017 17:36:48 +0100, Denis Laxalde wrote:
>> Yuya Nishihara a écrit :
>>> On Tue, 28 Feb 2017 16:00:44 -0800, Gregory Szorc wrote:
>>>> Also, the page it links to showing the revisions with links to a line range
>>>> filtered diff is good but not great. I imagine developers will complain
>>>> that it requires too many clicks to go to what they want (the actual line
>>>> content). IMO it would be *really* useful if there were a single page
>>>> showing multiple changesets and line range data for either/and a) raw line
>>>> content b) annotate view c) diff view. That way, developers could scroll
>>>> and see all versions of that line range. If you were looking at say the
>>>> history of a function, this would be *extremely* useful compared to loading
>>>> N pages. This improvement could be done as a follow-up and doesn't need to
>>>> block this series.
>>>
>>> When the page gets more dynamically rendered, will we still need the mdiff
>>> filtering function? I guess filtering will be handled by js, and we'll need
>>> to label all chunks instead of filtering them on server side.
>>>
>>> I think the mdiff and patch.diff() APIs are the most controversial part in
>>> this series. If we can go without them, that would be nice.
>>>
>>
>> Maybe filtering can occur in webutil.diffs only, but I seems to me that
>> this would imply parsing diff headers (@@ +start,count -start,count @@).
>> I think that filtering in mdiff/patch.diff is cleaner but I agree that
>> the API is not nice. Also, working on mdiff/patch.diff side will
>> probably be necessary when implementing the command-line version of this
>> feature.
>
> One idea is to attach a context to each chunk so we can filter them later or
> embed in <span data-xxx=""> attribute. For example,
>
>   patch.diff() yields text
>   patch.diffchunks() yields (text, some_line_information)
>

Following your idea, I'll send a series adding a diffhunks function in
patch module. I'll keep it outside this "hgweb" series as it is actually
self-consistent (and large). Once settle, I'll get back to the hgweb part.

Patch

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -902,6 +902,14 @@  def annotate(web, req, tmpl):
         for p in parentscache[rev]:
             yield p
 
+    def addlinerange(lines):
+        if lines:
+            linerange = webutil.formatlinerange(
+                lines[0]['lineno'] - 1, lines[-1]['lineno'])
+            for l in lines:
+                l['linerange'] = linerange
+                yield l
+
     def annotate(**map):
         if util.binary(fctx.data()):
             mt = (mimetypes.guess_type(fctx.path())[0]
@@ -917,7 +925,7 @@  def annotate(web, req, tmpl):
             rev = f.rev()
             if rev != previousrev:
                 # new block, yield lines from previous one
-                for linectx in block:
+                for linectx in addlinerange(block):
                     yield linectx
                 block = []
                 blockhead = True
@@ -943,7 +951,7 @@  def annotate(web, req, tmpl):
                 "linenumber": "% 6d" % (lineno + 1),
                 "revdate": f.date(),
             })
-        for linectx in block:
+        for linectx in addlinerange(block):
             yield linectx
 
     return tmpl("fileannotate",
diff --git a/mercurial/templates/gitweb/map b/mercurial/templates/gitweb/map
--- a/mercurial/templates/gitweb/map
+++ b/mercurial/templates/gitweb/map
@@ -111,6 +111,9 @@  annotateline = '
         <div>parents: {parents%annotateparent}</div>
         <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a>
         <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a>
+        <div>
+          follow lines <a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}{if(sessionvars, "&", "?")}linerange={linerange}">{linerange}</a>
+        </div>
       </div>
     </td>
     <td><pre><a class="linenr" href="#{lineid}">{linenumber}</a></pre></td>
diff --git a/mercurial/templates/paper/map b/mercurial/templates/paper/map
--- a/mercurial/templates/paper/map
+++ b/mercurial/templates/paper/map
@@ -92,6 +92,9 @@  annotateline = '
         <div>parents: {parents%annotateparent}</div>
         <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a>
         <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a>
+        <div>
+          follow lines <a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}{if(sessionvars, '&', '?')}linerange={linerange}">{linerange}</a>
+        </div>
       </div>
     </td>
     <td class="source"><a href="#{lineid}">{linenumber}</a> {line|escape}</td>
diff --git a/tests/test-hgweb-symrev.t b/tests/test-hgweb-symrev.t
--- a/tests/test-hgweb-symrev.t
+++ b/tests/test-hgweb-symrev.t
@@ -194,11 +194,13 @@  Set up the repo
   <a href="/annotate/43c799df6e75/foo?style=paper#l1">
   <a href="/diff/43c799df6e75/foo?style=paper">diff</a>
   <a href="/rev/43c799df6e75?style=paper">changeset</a>
+  follow lines <a href="/log/43c799df6e75/foo?style=paper&linerange=1:1">1:1</a>
   <a href="/annotate/a7c1559b7bba/foo?style=paper#l2">
   <a href="/annotate/a7c1559b7bba/foo?style=paper#l2">
   <a href="/annotate/43c799df6e75/foo?style=paper">0</a></div>
   <a href="/diff/a7c1559b7bba/foo?style=paper">diff</a>
   <a href="/rev/a7c1559b7bba?style=paper">changeset</a>
+  follow lines <a href="/log/a7c1559b7bba/foo?style=paper&linerange=2:2">2:2</a>
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=paper' | egrep $REVLINKS
   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
@@ -389,11 +391,13 @@  Set up the repo
   <a href="/annotate/43c799df6e75/foo?style=coal#l1">
   <a href="/diff/43c799df6e75/foo?style=coal">diff</a>
   <a href="/rev/43c799df6e75?style=coal">changeset</a>
+  follow lines <a href="/log/43c799df6e75/foo?style=coal&linerange=1:1">1:1</a>
   <a href="/annotate/a7c1559b7bba/foo?style=coal#l2">
   <a href="/annotate/a7c1559b7bba/foo?style=coal#l2">
   <a href="/annotate/43c799df6e75/foo?style=coal">0</a></div>
   <a href="/diff/a7c1559b7bba/foo?style=coal">diff</a>
   <a href="/rev/a7c1559b7bba?style=coal">changeset</a>
+  follow lines <a href="/log/a7c1559b7bba/foo?style=coal&linerange=2:2">2:2</a>
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=coal' | egrep $REVLINKS
   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
@@ -636,11 +640,13 @@  Set up the repo
   <a href="/annotate/43c799df6e75/foo?style=gitweb#l1">
   <a href="/diff/43c799df6e75/foo?style=gitweb">diff</a>
   <a href="/rev/43c799df6e75?style=gitweb">changeset</a>
+  follow lines <a href="/log/43c799df6e75/foo?style=gitweb&linerange=1:1">1:1</a>
   <a href="/annotate/a7c1559b7bba/foo?style=gitweb#l2">
   <a href="/annotate/a7c1559b7bba/foo?style=gitweb#l2">
   <a href="/annotate/43c799df6e75/foo?style=gitweb">0</a></div>
   <a href="/diff/a7c1559b7bba/foo?style=gitweb">diff</a>
   <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a>
+  follow lines <a href="/log/a7c1559b7bba/foo?style=gitweb&linerange=2:2">2:2</a>
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=gitweb' | egrep $REVLINKS
   <a href="/file/xyzzy?style=gitweb">files</a> |
diff --git a/tests/test-highlight.t b/tests/test-highlight.t
--- a/tests/test-highlight.t
+++ b/tests/test-highlight.t
@@ -303,6 +303,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l1">     1</a> <span class="c">#!/usr/bin/env python</span></td>
@@ -320,6 +323,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l2">     2</a> </td>
@@ -337,6 +343,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l3">     3</a> <span class="sd">&quot;&quot;&quot;Fun with generators. Corresponding Haskell implementation:</span></td>
@@ -354,6 +363,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l4">     4</a> </td>
@@ -371,6 +383,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l5">     5</a> <span class="sd">primes = 2 : sieve [3, 5..]</span></td>
@@ -388,6 +403,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l6">     6</a> <span class="sd">    where sieve (p:ns) = p : sieve [n | n &lt;- ns, mod n p /= 0]</span></td>
@@ -405,6 +423,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l7">     7</a> <span class="sd">&quot;&quot;&quot;</span></td>
@@ -422,6 +443,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l8">     8</a> </td>
@@ -439,6 +463,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l9">     9</a> <span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">dropwhile</span><span class="p">,</span> <span class="n">ifilter</span><span class="p">,</span> <span class="n">islice</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">chain</span></td>
@@ -456,6 +483,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l10">    10</a> </td>
@@ -473,6 +503,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l11">    11</a> <span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></td>
@@ -490,6 +523,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l12">    12</a>     <span class="sd">&quot;&quot;&quot;Generate all primes.&quot;&quot;&quot;</span></td>
@@ -507,6 +543,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l13">    13</a>     <span class="kn">def</span> <span class="nf">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -524,6 +563,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l14">    14</a>         <span class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span class="o">.</span><span class="n">next</span><span class="p">()</span></td>
@@ -541,6 +583,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l15">    15</a>         <span class="c"># It is important to yield *here* in order to stop the</span></td>
@@ -558,6 +603,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l16">    16</a>         <span class="c"># infinite recursion.</span></td>
@@ -575,6 +623,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l17">    17</a>         <span class="kn">yield</span> <span class="n">p</span></td>
@@ -592,6 +643,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l18">    18</a>         <span class="n">ns</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ns</span><span class="p">)</span></td>
@@ -609,6 +663,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l19">    19</a>         <span class="kn">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -626,6 +683,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l20">    20</a>             <span class="kn">yield</span> <span class="n">n</span></td>
@@ -643,6 +703,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l21">    21</a> </td>
@@ -660,6 +723,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l22">    22</a>     <span class="n">odds</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="n">count</span><span class="p">())</span></td>
@@ -677,6 +743,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l23">    23</a>     <span class="kn">return</span> <span class="n">chain</span><span class="p">([</span><span class="mi">2</span><span class="p">],</span> <span class="n">sieve</span><span class="p">(</span><span class="n">dropwhile</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">,</span> <span class="n">odds</span><span class="p">)))</span></td>
@@ -694,6 +763,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l24">    24</a> </td>
@@ -711,6 +783,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l25">    25</a> <span class="kn">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span></td>
@@ -728,6 +803,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l26">    26</a>     <span class="kn">import</span> <span class="nn">sys</span></td>
@@ -745,6 +823,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l27">    27</a>     <span class="kn">try</span><span class="p">:</span></td>
@@ -762,6 +843,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l28">    28</a>         <span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></td>
@@ -779,6 +863,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l29">    29</a>     <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></td>
@@ -796,6 +883,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l30">    30</a>         <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></td>
@@ -813,6 +903,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l31">    31</a>     <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></td>
@@ -830,6 +923,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l32">    32</a>     <span class="kn">print</span> <span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></td>
@@ -847,6 +943,9 @@  hgweb fileannotate, html
   <div>parents: </div>
   <a href="/diff/06824edf55d0/primes.py">diff</a>
   <a href="/rev/06824edf55d0">changeset</a>
+  <div>
+  follow lines <a href="/log/06824edf55d0/primes.py?linerange=1:33">1:33</a>
+  </div>
   </div>
   </td>
   <td class="source"><a href="#l33">    33</a> </td>