revset: support ranges in #generations relation
authorAnton Shestakov <av6@dwimlabs.net>
Tue, 15 Jan 2019 22:57:09 +0800
changeset 41359 431cf2c8c839
parent 41358 0a0927f7549d
child 41360 784ab13b340f
revset: support ranges in #generations relation
mercurial/dagop.py
mercurial/revset.py
tests/test-doctest.py
tests/test-revset.t
--- a/mercurial/dagop.py	Thu Jan 24 18:14:39 2019 +0300
+++ b/mercurial/dagop.py	Tue Jan 15 22:57:09 2019 +0800
@@ -28,7 +28,7 @@
 generatorset = smartset.generatorset
 
 # possible maximum depth between null and wdir()
-_maxlogdepth = 0x80000000
+maxlogdepth = 0x80000000
 
 def _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse):
     """Walk DAG using 'pfunc' from the given 'revs' nodes
@@ -42,7 +42,7 @@
     if startdepth is None:
         startdepth = 0
     if stopdepth is None:
-        stopdepth = _maxlogdepth
+        stopdepth = maxlogdepth
     if stopdepth == 0:
         return
     if stopdepth < 0:
@@ -221,7 +221,7 @@
     Scan ends at the stopdepth (exlusive) if specified. Revisions found
     earlier than the startdepth are omitted.
     """
-    if startdepth is None and stopdepth is None:
+    if startdepth is None and (stopdepth is None or stopdepth == maxlogdepth):
         gen = _genrevdescendants(repo, revs, followfirst)
     else:
         gen = _genrevdescendantsofdepth(repo, revs, followfirst,
--- a/mercurial/revset.py	Thu Jan 24 18:14:39 2019 +0300
+++ b/mercurial/revset.py	Tue Jan 15 22:57:09 2019 +0800
@@ -225,24 +225,82 @@
 def relationset(repo, subset, x, y, order):
     raise error.ParseError(_("can't use a relation in this context"))
 
-def generationsrel(repo, subset, x, rel, n, order):
-    # TODO: support range, rewrite tests, and drop startdepth argument
-    # from ancestors() and descendants() predicates
-    if n <= 0:
-        n = -n
-        return _ancestors(repo, subset, x, startdepth=n, stopdepth=n + 1)
-    else:
-        return _descendants(repo, subset, x, startdepth=n, stopdepth=n + 1)
+def _splitrange(a, b):
+    """Split range with bounds a and b into two ranges at 0 and return two
+    tuples of numbers for use as startdepth and stopdepth arguments of
+    revancestors and revdescendants.
+
+    >>> _splitrange(-10, -5)     # [-10:-5]
+    ((5, 11), (None, None))
+    >>> _splitrange(5, 10)       # [5:10]
+    ((None, None), (5, 11))
+    >>> _splitrange(-10, 10)     # [-10:10]
+    ((0, 11), (0, 11))
+    >>> _splitrange(-10, 0)      # [-10:0]
+    ((0, 11), (None, None))
+    >>> _splitrange(0, 10)       # [0:10]
+    ((None, None), (0, 11))
+    >>> _splitrange(0, 0)        # [0:0]
+    ((0, 1), (None, None))
+    >>> _splitrange(1, -1)       # [1:-1]
+    ((None, None), (None, None))
+    """
+    ancdepths = (None, None)
+    descdepths = (None, None)
+    if a == b == 0:
+        ancdepths = (0, 1)
+    if a < 0:
+        ancdepths = (-min(b, 0), -a + 1)
+    if b > 0:
+        descdepths = (max(a, 0), b + 1)
+    return ancdepths, descdepths
+
+def generationsrel(repo, subset, x, rel, a, b, order):
+    # TODO: rewrite tests, and drop startdepth argument from ancestors() and
+    # descendants() predicates
+    (ancstart, ancstop), (descstart, descstop) = _splitrange(a, b)
+
+    if ancstart is None and descstart is None:
+        return baseset()
+
+    revs = getset(repo, fullreposet(repo), x)
+    if not revs:
+        return baseset()
+
+    if ancstart is not None and descstart is not None:
+        s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
+        s += dagop.revdescendants(repo, revs, False, descstart, descstop)
+    elif ancstart is not None:
+        s = dagop.revancestors(repo, revs, False, ancstart, ancstop)
+    elif descstart is not None:
+        s = dagop.revdescendants(repo, revs, False, descstart, descstop)
+
+    return subset & s
 
 def relsubscriptset(repo, subset, x, y, z, order):
     # this is pretty basic implementation of 'x#y[z]' operator, still
     # experimental so undocumented. see the wiki for further ideas.
     # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
     rel = getsymbol(y)
-    n = getinteger(z, _("relation subscript must be an integer"))
+    try:
+        a, b = getrange(z, '')
+    except error.ParseError:
+        a = getinteger(z, _("relation subscript must be an integer"))
+        b = a
+    else:
+        def getbound(i):
+            if i is None:
+                return None
+            msg = _("relation subscript bounds must be integers")
+            return getinteger(i, msg)
+        a, b = [getbound(i) for i in (a, b)]
+        if a is None:
+            a = -(dagop.maxlogdepth - 1)
+        if b is None:
+            b = +(dagop.maxlogdepth - 1)
 
     if rel in subscriptrelations:
-        return subscriptrelations[rel](repo, subset, x, rel, n, order)
+        return subscriptrelations[rel](repo, subset, x, rel, a, b, order)
 
     relnames = [r for r in subscriptrelations.keys() if len(r) > 1]
     raise error.UnknownIdentifier(rel, relnames)
--- a/tests/test-doctest.py	Thu Jan 24 18:14:39 2019 +0300
+++ b/tests/test-doctest.py	Tue Jan 15 22:57:09 2019 +0800
@@ -62,6 +62,7 @@
 testmod('mercurial.pycompat')
 testmod('mercurial.revlog')
 testmod('mercurial.revlogutils.deltas')
+testmod('mercurial.revset')
 testmod('mercurial.revsetlang')
 testmod('mercurial.smartset')
 testmod('mercurial.store')
--- a/tests/test-revset.t	Thu Jan 24 18:14:39 2019 +0300
+++ b/tests/test-revset.t	Tue Jan 15 22:57:09 2019 +0800
@@ -648,6 +648,9 @@
   $ hg debugrevspec '.#generations[1-2]'
   hg: parse error: relation subscript must be an integer
   [255]
+  $ hg debugrevspec '.#generations[foo:bar]'
+  hg: parse error: relation subscript bounds must be integers
+  [255]
 
 suggested relations
 
@@ -1274,6 +1277,30 @@
   $ log '.#g[(-1)]'
   8
 
+  $ log '6#generations[0:1]'
+  6
+  7
+  $ log '6#generations[-1:1]'
+  4
+  5
+  6
+  7
+  $ log '6#generations[0:]'
+  6
+  7
+  $ log '5#generations[:0]'
+  0
+  1
+  3
+  5
+  $ log '3#generations[:]'
+  0
+  1
+  3
+  5
+  6
+  7
+
   $ hg debugrevspec -p parsed 'roots(:)#g[2]'
   * parsed:
   (relsubscript