diff mercurial/revset.py @ 30719:42c75b4fa46a

revset: add a followlines(file, fromline, toline[, rev]) revset This revset returns the history of a range of lines (fromline, toline) of a file starting from `rev` or the current working directory. Added tests in test-annotate.t which already contains a reasonably complex repository.
author Denis Laxalde <denis.laxalde@logilab.fr>
date Wed, 04 Jan 2017 16:47:49 +0100
parents 7e4f431206cd
children c3a3896a9fa8
line wrap: on
line diff
--- a/mercurial/revset.py	Wed Dec 28 23:03:37 2016 +0100
+++ b/mercurial/revset.py	Wed Jan 04 16:47:49 2017 +0100
@@ -1068,6 +1068,52 @@
     # of every revisions or files revisions.
     return _follow(repo, subset, x, '_followfirst', followfirst=True)
 
+@predicate('followlines(file, fromline, toline[, rev=.])', safe=True)
+def followlines(repo, subset, x):
+    """Changesets modifying `file` in line range ('fromline', 'toline').
+
+    Line range corresponds to 'file' content at 'rev' and should hence be
+    consistent with file size. If rev is not specified, working directory's
+    parent is used.
+    """
+    from . import context  # avoid circular import issues
+
+    args = getargs(x, 3, 4, _("followlines takes at least three arguments"))
+
+    rev = '.'
+    if len(args) == 4:
+        revarg = getargsdict(args[3], 'followlines', 'rev')
+        if 'rev' in revarg:
+            revs = getset(repo, fullreposet(repo), revarg['rev'])
+            if len(revs) != 1:
+                raise error.ParseError(
+                    _("followlines expects exactly one revision"))
+            rev = revs.last()
+
+    pat = getstring(args[0], _("followlines requires a pattern"))
+    if not matchmod.patkind(pat):
+        fname = pathutil.canonpath(repo.root, repo.getcwd(), pat)
+    else:
+        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[rev])
+        files = [f for f in repo[rev] if m(f)]
+        if len(files) != 1:
+            raise error.ParseError(_("followlines expects exactly one file"))
+        fname = files[0]
+
+    try:
+        fromline, toline = [int(getsymbol(a)) for a in args[1:3]]
+    except ValueError:
+        raise error.ParseError(_("line range bounds must be integers"))
+    if toline - fromline < 0:
+        raise error.ParseError(_("line range must be positive"))
+    if fromline < 1:
+        raise error.ParseError(_("fromline must be strictly positive"))
+    fromline -= 1
+
+    fctx = repo[rev].filectx(fname)
+    revs = (c.rev() for c in context.blockancestors(fctx, fromline, toline))
+    return subset & generatorset(revs, iterasc=False)
+
 @predicate('all()', safe=True)
 def getall(repo, subset, x):
     """All changesets, the same as ``0:tip``.