revset: lookup descendents for negative arguments to ancestor operator
authorDavid Soria Parra <davidsp@fb.com>
Sat, 27 May 2017 10:25:09 -0700
changeset 32699 f75d0aa5dc83
parent 32698 1b5c61d38a52
child 32700 3afe258fb0fe
revset: lookup descendents for negative arguments to ancestor operator Negative offsets to the `~` operator now search for descendents. The search is aborted when a node has more than one child as we do not have a definition for 'nth child'. Optionally we can introduce such a notion and take the nth child ordered by rev number. The current revset language does provides a short operator for ancestor lookup but not for descendents. This gives user a simple revset to move to the previous changeset, e.g. `hg up '.~1'` but not to the 'next' changeset. With this change userse can now use `.~-1` as a shortcut to move to the next changeset. This fits better into allowing users to specify revisions via revsets and avoiding the need for special `hg next` and `hg prev` operations. The alternative to negative offsets is adding a new operator. We do not have many operators in ascii left that do not require bash escaping (',', '_', and '/' come to mind). If we decide that we should add a more convenient short operator such as ('/', e.g. './1') we can later add it and allow ascendents lookup via negative numbers.
mercurial/help/revisions.txt
mercurial/revset.py
tests/test-revset.t
--- a/mercurial/help/revisions.txt	Tue Jun 06 22:17:39 2017 +0530
+++ b/mercurial/help/revisions.txt	Sat May 27 10:25:09 2017 -0700
@@ -97,6 +97,7 @@
 
 ``x~n``
   The nth first ancestor of x; ``x~0`` is x; ``x~3`` is ``x^^^``.
+  For n < 0, the nth unambiguous descendent of x.
 
 ``x ## y``
   Concatenate strings and identifiers into one string.
--- a/mercurial/revset.py	Tue Jun 06 22:17:39 2017 +0530
+++ b/mercurial/revset.py	Sat May 27 10:25:09 2017 -0700
@@ -379,12 +379,33 @@
     # Like ``ancestors(set)`` but follows only the first parents.
     return _ancestors(repo, subset, x, followfirst=True)
 
+def _childrenspec(repo, subset, x, n, order):
+    """Changesets that are the Nth child of a changeset
+    in set.
+    """
+    cs = set()
+    for r in getset(repo, fullreposet(repo), x):
+        for i in range(n):
+            c = repo[r].children()
+            if len(c) == 0:
+                break
+            if len(c) > 1:
+                raise error.RepoLookupError(
+                    _("revision in set has more than one child"))
+            r = c[0]
+        else:
+            cs.add(r)
+    return subset & cs
+
 def ancestorspec(repo, subset, x, n, order):
     """``set~n``
     Changesets that are the Nth ancestor (first parents only) of a changeset
     in set.
     """
     n = getinteger(n, _("~ expects a number"))
+    if n < 0:
+        # children lookup
+        return _childrenspec(repo, subset, x, -n, order)
     ps = set()
     cl = repo.changelog
     for r in getset(repo, fullreposet(repo), x):
--- a/tests/test-revset.t	Tue Jun 06 22:17:39 2017 +0530
+++ b/tests/test-revset.t	Sat May 27 10:25:09 2017 -0700
@@ -2975,6 +2975,14 @@
   $ log 'merge()^^^'
   1
 
+  $ log '(merge() | 0)~-1'
+  7
+  1
+  $ log 'merge()~-1'
+  7
+  $ log 'tip~-1'
+  $ log '(tip | merge())~-1'
+  7
   $ log 'merge()~0'
   6
   $ log 'merge()~1'
@@ -2995,6 +3003,10 @@
   hg: parse error: ^ expects a number 0, 1, or 2
   [255]
 
+  $ log 'branchpoint()~-1'
+  abort: revision in set has more than one child!
+  [255]
+
 Bogus function gets suggestions
   $ log 'add()'
   hg: parse error: unknown identifier: add