revset: add a 'descend' argument to followlines to return descendants
authorDenis Laxalde <denis.laxalde@logilab.fr>
Mon, 16 Jan 2017 09:24:47 +0100
changeset 31938 5e3b49defbff
parent 31937 826e600605f6
child 31939 604d31507f86
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).
mercurial/revset.py
tests/test-annotate.t
--- a/mercurial/revset.py	Mon Apr 10 15:11:36 2017 +0200
+++ b/mercurial/revset.py	Mon Jan 16 09:24:47 2017 +0100
@@ -901,17 +901,22 @@
     # 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 @@
     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):
--- a/tests/test-annotate.t	Mon Apr 10 15:11:36 2017 +0200
+++ b/tests/test-annotate.t	Mon Jan 16 09:24:47 2017 +0100
@@ -484,7 +484,9 @@
   $ 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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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