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.
--- 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