Patchwork [2,of,4] revset: add a 'descend' argument to followlines to return descendants

login
register
mail settings
Submitter Denis Laxalde
Date April 11, 2017, 1:09 p.m.
Message ID <182fa6fb647dcfac8ebc.1491916149@sh77.tls.logilab.fr>
Download mbox | patch
Permalink /patch/20105/
State Accepted
Headers show

Comments

Denis Laxalde - April 11, 2017, 1:09 p.m.
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1484555087 -3600
#      Mon Jan 16 09:24:47 2017 +0100
# Node ID 182fa6fb647dcfac8ebcc6a3a6646be43122b2b9
# Parent  ce5fd23baea30c83d668a8680d2b6ed0ef7baa14
# Available At http://hg.logilab.org/users/dlaxalde/hg
#              hg pull http://hg.logilab.org/users/dlaxalde/hg -r 182fa6fb647d
revset: add a 'descend' argument to followlines to return descendants

This is useful to follow changes in a block of lines forward in the history
(for instance, when one wants to find out how a function evolved from a point
in history).

We added a 'descend' parameter to followlines(), which defaults to False. If
True, followlines() returns descendants of startrev.

Because context.blockdescendants() does not follow renames, these are not
followed by the revset either, so history will end when a rename occurs (as
can be seen in tests).
Yuya Nishihara - April 13, 2017, 3:55 p.m.
On Tue, 11 Apr 2017 15:09:09 +0200, Denis Laxalde wrote:
> # HG changeset patch
> # User Denis Laxalde <denis.laxalde@logilab.fr>
> # Date 1484555087 -3600
> #      Mon Jan 16 09:24:47 2017 +0100
> # Node ID 182fa6fb647dcfac8ebcc6a3a6646be43122b2b9
> # Parent  ce5fd23baea30c83d668a8680d2b6ed0ef7baa14
> # Available At http://hg.logilab.org/users/dlaxalde/hg
> #              hg pull http://hg.logilab.org/users/dlaxalde/hg -r 182fa6fb647d
> revset: add a 'descend' argument to followlines to return descendants

> -@predicate('followlines(file, fromline:toline[, startrev=.])', safe=True)
> +@predicate('followlines(file, fromline:toline[, startrev=., descend=False])',
> +           safe=True)
>  def followlines(repo, subset, x):
>      """Changesets modifying `file` in line range ('fromline', 'toline').
>  
>      Line range corresponds to 'file' content at 'startrev' and should hence be
>      consistent with file size. If startrev is not specified, working directory's
>      parent is used.
> +
> +    By default, ancestors of 'startrev' are returned. If 'descend' is True,
> +    descendants of 'startrev' are returned though renames are (currently) not
> +    followed in this direction.
>      """
>      from . import context  # avoid circular import issues
>  
> -    args = getargsdict(x, 'followlines', 'file *lines startrev')
> +    args = getargsdict(x, 'followlines', 'file *lines startrev descend')
>      if len(args['lines']) != 1:
>          raise error.ParseError(_("followlines requires a line range"))
>  
> @@ -939,9 +944,17 @@ def followlines(repo, subset, x):
>      fromline, toline = util.processlinerange(fromline, toline)
>  
>      fctx = repo[rev].filectx(fname)
> -    revs = (c.rev() for c, _linerange
> -            in context.blockancestors(fctx, fromline, toline))
> -    return subset & generatorset(revs, iterasc=False)
> +    if args.get('descend', False):

args['descend'] isn't a boolean but a parsed tree. I'll send a follow up this
weekend.

Patch

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -901,17 +901,22 @@  def _followfirst(repo, subset, x):
     # of every revisions or files revisions.
     return _follow(repo, subset, x, '_followfirst', followfirst=True)
 
-@predicate('followlines(file, fromline:toline[, startrev=.])', safe=True)
+@predicate('followlines(file, fromline:toline[, startrev=., descend=False])',
+           safe=True)
 def followlines(repo, subset, x):
     """Changesets modifying `file` in line range ('fromline', 'toline').
 
     Line range corresponds to 'file' content at 'startrev' and should hence be
     consistent with file size. If startrev is not specified, working directory's
     parent is used.
+
+    By default, ancestors of 'startrev' are returned. If 'descend' is True,
+    descendants of 'startrev' are returned though renames are (currently) not
+    followed in this direction.
     """
     from . import context  # avoid circular import issues
 
-    args = getargsdict(x, 'followlines', 'file *lines startrev')
+    args = getargsdict(x, 'followlines', 'file *lines startrev descend')
     if len(args['lines']) != 1:
         raise error.ParseError(_("followlines requires a line range"))
 
@@ -939,9 +944,17 @@  def followlines(repo, subset, x):
     fromline, toline = util.processlinerange(fromline, toline)
 
     fctx = repo[rev].filectx(fname)
-    revs = (c.rev() for c, _linerange
-            in context.blockancestors(fctx, fromline, toline))
-    return subset & generatorset(revs, iterasc=False)
+    if args.get('descend', False):
+        rs = generatorset(
+            (c.rev() for c, _linerange
+             in context.blockdescendants(fctx, fromline, toline)),
+            iterasc=True)
+    else:
+        rs = generatorset(
+            (c.rev() for c, _linerange
+             in context.blockancestors(fctx, fromline, toline)),
+            iterasc=False)
+    return subset & rs
 
 @predicate('all()', safe=True)
 def getall(repo, subset, x):
diff --git a/tests/test-annotate.t b/tests/test-annotate.t
--- a/tests/test-annotate.t
+++ b/tests/test-annotate.t
@@ -484,7 +484,9 @@  annotate removed file
   $ hg id -n
   20
 
-Test followlines() revset
+Test followlines() revset; we usually check both followlines(pat, range) and
+followlines(pat, range, descend=True) to make sure both give the same result
+when they should.
 
   $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5)'
   16: baz:0
@@ -494,9 +496,11 @@  Test followlines() revset
   16: baz:0
   19: baz:3
   20: baz:4
-  $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=.^)'
+  $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19)'
   16: baz:0
   19: baz:3
+  $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=True)'
+  20: baz:4
   $ printf "0\n0\n" | cat - baz > baz1
   $ mv baz1 baz
   $ hg ci -m 'added two lines with 0'
@@ -504,12 +508,16 @@  Test followlines() revset
   16: baz:0
   19: baz:3
   20: baz:4
+  $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, descend=True, startrev=19)'
+  20: baz:4
   $ echo 6 >> baz
   $ hg ci -m 'added line 8'
   $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
   16: baz:0
   19: baz:3
   20: baz:4
+  $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=True)'
+  20: baz:4
   $ sed 's/3/3+/' baz > baz.new
   $ mv baz.new baz
   $ hg ci -m 'baz:3->3+'
@@ -518,6 +526,9 @@  Test followlines() revset
   19: baz:3
   20: baz:4
   23: baz:3->3+
+  $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=True)'
+  20: baz:4
+  23: baz:3->3+
   $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 1:2)'
   21: added two lines with 0
 
@@ -536,9 +547,13 @@  renames are followed
   20: baz:4
   23: baz:3->3+
   24: qux:4->4+
-  $ hg up 23 --quiet
+
+but are missed when following children
+  $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=22, descend=True)'
+  23: baz:3->3+
 
 merge
+  $ hg up 23 --quiet
   $ echo 7 >> baz
   $ hg ci -m 'one more line, out of line range'
   created new head
@@ -581,6 +596,10 @@  merge
   28: merge from other side
   $ hg up 23 --quiet
 
+we are missing the branch with rename when following children
+  $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=25, descend=True)'
+  26: baz:3+->3-
+
 check error cases
   $ hg log -r 'followlines()'
   hg: parse error: followlines takes at least 1 positional arguments