Patchwork [V5] revset: support ranges in #generations relation

login
register
mail settings
Submitter Anton Shestakov
Date Jan. 17, 2019, 1:17 p.m.
Message ID <d1e7a57a62ca4dd99a11.1547731046@neuro>
Download mbox | patch
Permalink /patch/37860/
State Superseded
Headers show

Comments

Anton Shestakov - Jan. 17, 2019, 1:17 p.m.
# HG changeset patch
# User Anton Shestakov <av6@dwimlabs.net>
# Date 1547564229 -28800
#      Tue Jan 15 22:57:09 2019 +0800
# Node ID d1e7a57a62ca4dd99a11659148baf672bc9cab38
# Parent  8aca89a694d4bd7d25877b3652fb83e187ea1802
revset: support ranges in #generations relation
Yuya Nishihara - Jan. 17, 2019, 2:28 p.m.
On Thu, 17 Jan 2019 21:17:26 +0800, Anton Shestakov wrote:
> # HG changeset patch
> # User Anton Shestakov <av6@dwimlabs.net>
> # Date 1547564229 -28800
> #      Tue Jan 15 22:57:09 2019 +0800
> # Node ID d1e7a57a62ca4dd99a11659148baf672bc9cab38
> # Parent  8aca89a694d4bd7d25877b3652fb83e187ea1802
> revset: support ranges in #generations relation

> +def _splitrange(a, b):
> +    """ Split range with bounds a and b into two ranges at 0 and return two
> +    lists of numbers for use as startdepth and stopdepth arguments of
> +    _ancestors and _descendants.
> +
> +    If 0 is in the input range, it is included only in the second list.
> +
> +    >>> _splitrange(None, None)  # [:]
> +    ([1, None], [0, None])
> +    >>> _splitrange(-10, None)   # [-10:]
> +    ([1, 11], [0, None])
> +    >>> _splitrange(None, 10)    # [:10]
> +    ([1, None], [0, 11])
> +    >>> _splitrange(-10, -5)     # [-10:-5]
> +    ([5, 11], [None, None])
> +    >>> _splitrange(5, 10)       # [5:10]
> +    ([None, None], [5, 11])
> +    >>> _splitrange(-10, 10)     # [-10:10]
> +    ([1, 11], [0, 11])

> +    >>> _splitrange(-10, 0)      # [-10:0]
> +    ([1, 11], [0, 1])

So this would result in silly computation 'anc + desc'?
As I said 'anc{x} + desc{x}' won't yield 'x' twice, so including 0 in both
sets should be fine.

(Just spotted. I have no time to review this and the other interesting patches
today.)
Anton Shestakov - Jan. 18, 2019, 10 a.m.
On Thu, 17 Jan 2019 23:28:22 +0900
Yuya Nishihara <yuya@tcha.org> wrote:

> On Thu, 17 Jan 2019 21:17:26 +0800, Anton Shestakov wrote:
> > # HG changeset patch
> > # User Anton Shestakov <av6@dwimlabs.net>
> > # Date 1547564229 -28800
> > #      Tue Jan 15 22:57:09 2019 +0800
> > # Node ID d1e7a57a62ca4dd99a11659148baf672bc9cab38
> > # Parent  8aca89a694d4bd7d25877b3652fb83e187ea1802
> > revset: support ranges in #generations relation
> 
> > +def _splitrange(a, b):
> > +    """ Split range with bounds a and b into two ranges at 0 and return two
> > +    lists of numbers for use as startdepth and stopdepth arguments of
> > +    _ancestors and _descendants.
> > +
> > +    If 0 is in the input range, it is included only in the second list.
> > +
> > +    >>> _splitrange(None, None)  # [:]
> > +    ([1, None], [0, None])
> > +    >>> _splitrange(-10, None)   # [-10:]
> > +    ([1, 11], [0, None])
> > +    >>> _splitrange(None, 10)    # [:10]
> > +    ([1, None], [0, 11])
> > +    >>> _splitrange(-10, -5)     # [-10:-5]
> > +    ([5, 11], [None, None])
> > +    >>> _splitrange(5, 10)       # [5:10]
> > +    ([None, None], [5, 11])
> > +    >>> _splitrange(-10, 10)     # [-10:10]
> > +    ([1, 11], [0, 11])
> 
> > +    >>> _splitrange(-10, 0)      # [-10:0]
> > +    ([1, 11], [0, 1])
> 
> So this would result in silly computation 'anc + desc'?
> As I said 'anc{x} + desc{x}' won't yield 'x' twice, so including 0 in both
> sets should be fine.

It would be fine, but we don't want to call both _ancestors() and
_descendants() if we can get the 0 from just one of these functions.
This is improved in V6.

Patch

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -225,24 +225,93 @@  def notset(repo, subset, x, order):
 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
+    lists of numbers for use as startdepth and stopdepth arguments of
+    _ancestors and _descendants.
+
+    If 0 is in the input range, it is included only in the second list.
+
+    >>> _splitrange(None, None)  # [:]
+    ([1, None], [0, None])
+    >>> _splitrange(-10, None)   # [-10:]
+    ([1, 11], [0, None])
+    >>> _splitrange(None, 10)    # [:10]
+    ([1, None], [0, 11])
+    >>> _splitrange(-10, -5)     # [-10:-5]
+    ([5, 11], [None, None])
+    >>> _splitrange(5, 10)       # [5:10]
+    ([None, None], [5, 11])
+    >>> _splitrange(-10, 10)     # [-10:10]
+    ([1, 11], [0, 11])
+    >>> _splitrange(-10, 0)      # [-10:0]
+    ([1, 11], [0, 1])
+    >>> _splitrange(0, 10)       # [0:10]
+    ([None, None], [0, 11])
+    >>> _splitrange(1, -1)       # [1:-1]
+    ([None, None], [None, None])
+    """
+    depths = ([None, None], [None, None])
+
+    if a is None or a < 0:
+        if b is None or b >= 0:
+            depths[0][0] = 1
+        else:
+            depths[0][0] = -b
+        if a is None:
+            depths[0][1] = None
+        else:
+            depths[0][1] = -a + 1
+
+    if b is None or b >= 0:
+        if a is None or a < 0:
+            depths[1][0] = 0
+        else:
+            depths[1][0] = a
+        if b is None:
+            depths[1][1] = None
+        else:
+            depths[1][1] = b + 1
+
+    return depths
+
+def generationsrel(repo, subset, x, rel, a, b, order):
+    # TODO: rewrite tests, and drop startdepth argument from ancestors() and
+    # descendants() predicates
+    depths = _splitrange(a, b)
+    ancstart, ancstop = depths[0]
+    descstart, descstop = depths[1]
+
+    if ancstart is not None and descstart is not None:
+        return (_ancestors(repo, subset, x, False, ancstart, ancstop) +
+                _descendants(repo, subset, x, False, descstart, descstop))
+    elif ancstart is not None:
+        return _ancestors(repo, subset, x, False, ancstart, ancstop)
+    elif descstart is not None:
+        return _descendants(repo, subset, x, False, descstart, descstop)
+
+    return baseset()
 
 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 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)
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -62,6 +62,7 @@  testmod('mercurial.parser')
 testmod('mercurial.pycompat')
 testmod('mercurial.revlog')
 testmod('mercurial.revlogutils.deltas')
+testmod('mercurial.revset')
 testmod('mercurial.revsetlang')
 testmod('mercurial.smartset')
 testmod('mercurial.store')
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -648,6 +648,9 @@  parse errors of relation, subscript and 
   $ 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 @@  test ancestors/descendants relation subs
   $ 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