Patchwork [7,of,7] revset: add startdepth limit to ancestors() as internal option

login
register
mail settings
Submitter Yuya Nishihara
Date June 22, 2017, 3:52 p.m.
Message ID <043c544c05570f88a8e3.1498146740@mimosa>
Download mbox | patch
Permalink /patch/21618/
State Accepted
Headers show

Comments

Yuya Nishihara - June 22, 2017, 3:52 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1497714058 -32400
#      Sun Jun 18 00:40:58 2017 +0900
# Node ID 043c544c05570f88a8e39bc7a7ea5ba0db616a62
# Parent  055c465c0bb153c4d595a3c476f2e448c1d2d35b
revset: add startdepth limit to ancestors() as internal option

This is necessary to implement the set{gen} (set subscript) operator. For
example, set{-n} will be translated to ancestors(set, depth=n, startdepth=n).

https://www.mercurial-scm.org/wiki/RevsetOperatorPlan#ideas_from_mpm

The UI is undecided and I doubt if the startdepth option would be actually
useful, so the option is hidden for now. 'depth' could be extended to take
min:max range, in which case, integer depth should select a single generation.

  ancestors(set, depth=:y)  # scan up to y-th generation
  ancestors(set, depth=x:)  # skip until (x-1)-th generation
  ancestors(set, depth=x)   # select only x-th generation

Any ideas are welcomed.

  # reverse(ancestors(tip)) using hg repo
  3) 0.075951
  4) 0.076175
via Mercurial-devel - June 23, 2017, 6:19 p.m.
On Thu, Jun 22, 2017 at 8:52 AM, Yuya Nishihara <yuya@tcha.org> wrote:
> # HG changeset patch
> # User Yuya Nishihara <yuya@tcha.org>
> # Date 1497714058 -32400
> #      Sun Jun 18 00:40:58 2017 +0900
> # Node ID 043c544c05570f88a8e39bc7a7ea5ba0db616a62
> # Parent  055c465c0bb153c4d595a3c476f2e448c1d2d35b
> revset: add startdepth limit to ancestors() as internal option

Queued this, thanks.

>
> This is necessary to implement the set{gen} (set subscript) operator. For
> example, set{-n} will be translated to ancestors(set, depth=n, startdepth=n).
>
> https://www.mercurial-scm.org/wiki/RevsetOperatorPlan#ideas_from_mpm
>
> The UI is undecided and I doubt if the startdepth option would be actually
> useful, so the option is hidden for now. 'depth' could be extended to take
> min:max range, in which case, integer depth should select a single generation.
>
>   ancestors(set, depth=:y)  # scan up to y-th generation
>   ancestors(set, depth=x:)  # skip until (x-1)-th generation
>   ancestors(set, depth=x)   # select only x-th generation
>
> Any ideas are welcomed.

I also don't see the point of startdepth, but I have never even missed
stopdepth myself so maybe I'm not the right person to ask.

Patch

diff --git a/mercurial/dagop.py b/mercurial/dagop.py
--- a/mercurial/dagop.py
+++ b/mercurial/dagop.py
@@ -23,11 +23,13 @@  generatorset = smartset.generatorset
 # possible maximum depth between null and wdir()
 _maxlogdepth = 0x80000000
 
-def _genrevancestors(repo, revs, followfirst, stopdepth):
+def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth):
     if followfirst:
         cut = 1
     else:
         cut = None
+    if startdepth is None:
+        startdepth = 0
     if stopdepth is None:
         stopdepth = _maxlogdepth
     if stopdepth <= 0:
@@ -53,8 +55,10 @@  def _genrevancestors(repo, revs, followf
             inputrev = next(irevs, None)
             if inputrev is not None:
                 heapq.heappush(pendingheap, (-inputrev, 0))
+        # rescan parents until curdepth >= startdepth because queued entries
+        # of the same revision are iterated from the lowest depth
         foundnew = (currev != lastrev)
-        if foundnew:
+        if foundnew and curdepth >= startdepth:
             lastrev = currev
             yield currev
         pdepth = curdepth + 1
@@ -68,13 +72,14 @@  def _genrevancestors(repo, revs, followf
                     if pctx.rev() != node.nullrev:
                         heapq.heappush(pendingheap, (-pctx.rev(), pdepth))
 
-def revancestors(repo, revs, followfirst, stopdepth=None):
+def revancestors(repo, revs, followfirst, startdepth=None, stopdepth=None):
     """Like revlog.ancestors(), but supports additional options, includes
     the given revs themselves, and returns a smartset
 
-    Scan ends at the stopdepth (exlusive) if specified.
+    Scan ends at the stopdepth (exlusive) if specified. Revisions found
+    earlier than the startdepth are omitted.
     """
-    gen = _genrevancestors(repo, revs, followfirst, stopdepth)
+    gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth)
     return generatorset(gen, iterasc=False)
 
 def revdescendants(repo, revs, followfirst):
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -238,11 +238,12 @@  def ancestor(repo, subset, x):
         return baseset([anc.rev()])
     return baseset()
 
-def _ancestors(repo, subset, x, followfirst=False, stopdepth=None):
+def _ancestors(repo, subset, x, followfirst=False, startdepth=None,
+               stopdepth=None):
     heads = getset(repo, fullreposet(repo), x)
     if not heads:
         return baseset()
-    s = dagop.revancestors(repo, heads, followfirst, stopdepth)
+    s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
     return subset & s
 
 @predicate('ancestors(set[, depth])', safe=True)
@@ -253,18 +254,26 @@  def ancestors(repo, subset, x):
     If depth is specified, the result only includes changesets up to
     the specified generation.
     """
-    args = getargsdict(x, 'ancestors', 'set depth')
+    # startdepth is for internal use only until we can decide the UI
+    args = getargsdict(x, 'ancestors', 'set depth startdepth')
     if 'set' not in args:
         # i18n: "ancestors" is a keyword
         raise error.ParseError(_('ancestors takes at least 1 argument'))
-    stopdepth = None
+    startdepth = stopdepth = None
+    if 'startdepth' in args:
+        n = getinteger(args['startdepth'],
+                       "ancestors expects an integer startdepth")
+        if n < 0:
+            raise error.ParseError("negative startdepth")
+        startdepth = n
     if 'depth' in args:
         # i18n: "ancestors" is a keyword
         n = getinteger(args['depth'], _("ancestors expects an integer depth"))
         if n < 0:
             raise error.ParseError(_("negative depth"))
         stopdepth = n + 1
-    return _ancestors(repo, subset, args['set'], stopdepth=stopdepth)
+    return _ancestors(repo, subset, args['set'],
+                      startdepth=startdepth, stopdepth=stopdepth)
 
 @predicate('_firstancestors', safe=True)
 def _firstancestors(repo, subset, x):
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -903,6 +903,29 @@  test ancestors with depth limit
   6
   5
 
+ (walk 2nd and 3rd ancestors)
+
+  $ log 'reverse(ancestors(7, depth=3, startdepth=2))'
+  5
+  4
+  3
+  2
+
+ (interleaved: '4' would be missing if higher-depth ancestors weren't scanned)
+
+  $ log 'reverse(ancestors(7+8, depth=2, startdepth=2))'
+  5
+  4
+  2
+
+ (note that 'ancestors(x, depth=y, startdepth=z)' does not identical to
+ 'ancestors(x, depth=y) - ancestors(x, depth=z-1)' because a node may have
+ multiple depths)
+
+  $ log 'reverse(ancestors(7+8, depth=2) - ancestors(7+8, depth=1))'
+  5
+  2
+
 test bad arguments passed to ancestors()
 
   $ log 'ancestors(., depth=-1)'