revset: define successors revset
authorJun Wu <quark@fb.com>
Mon, 10 Jul 2017 10:56:40 -0700
changeset 33377 5d63e5f40bea
parent 33376 d5a38eae67e5
child 33378 adf95bfb423a
revset: define successors revset This revset returns all successors, including transit nodes and the source nodes (to be consistent with existing revsets like "ancestors"). To filter out transit nodes, use `successors(X)-obsolete()`. To filter out divergent case, use `successors(X)-divergent()-obsolete()`. The revset could be useful to define rebase destination, like: `max(successors(BASE)-divergent()-obsolete())`. The `max` is to deal with splits. There are other implementations where `successors` returns just one level of successors, and `allsuccessors` returns everything. I think `successors` returning all successors by default is more user friendly. We have seen cases in production where people use 1-level `successors` while they really want `allsuccessors`. So it seems better to just have one single revset returning all successors by default to avoid user errors. In the future we might want to add `depth` keyword argument to it and for other revsets like `ancestors` etc. Or even build some flexible indexing syntax [1] to satisfy people having the depth limit requirement. [1]: https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-July/101140.html
mercurial/revset.py
tests/test-revset.t
--- a/mercurial/revset.py	Mon Jul 10 21:55:43 2017 -0700
+++ b/mercurial/revset.py	Mon Jul 10 10:56:40 2017 -0700
@@ -19,6 +19,7 @@
     match as matchmod,
     node,
     obsolete as obsmod,
+    obsutil,
     pathutil,
     phases,
     registrar,
@@ -1826,6 +1827,28 @@
 
     return subset.filter(matches, condrepr=('<subrepo %r>', pat))
 
+def _mapbynodefunc(repo, s, f):
+    """(repo, smartset, [node] -> [node]) -> smartset
+
+    Helper method to map a smartset to another smartset given a function only
+    talking about nodes. Handles converting between rev numbers and nodes, and
+    filtering.
+    """
+    cl = repo.unfiltered().changelog
+    torev = cl.rev
+    tonode = cl.node
+    nodemap = cl.nodemap
+    result = set(torev(n) for n in f(tonode(r) for r in s) if n in nodemap)
+    return smartset.baseset(result - repo.changelog.filteredrevs)
+
+@predicate('successors(set)', safe=True)
+def successors(repo, subset, x):
+    """All successors for set, including the given set themselves"""
+    s = getset(repo, fullreposet(repo), x)
+    f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
+    d = _mapbynodefunc(repo, s, f)
+    return subset & d
+
 def _substringmatcher(pattern, casesensitive=True):
     kind, pattern, matcher = util.stringmatcher(pattern,
                                                 casesensitive=casesensitive)
--- a/tests/test-revset.t	Mon Jul 10 21:55:43 2017 -0700
+++ b/tests/test-revset.t	Mon Jul 10 10:56:40 2017 -0700
@@ -20,6 +20,7 @@
   > EOF
   $ cat >> $HGRCPATH << EOF
   > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
   > testrevset=$TESTTMP/testrevset.py
   > EOF
 
@@ -4283,3 +4284,56 @@
   P=[3]
 
   $ cd ..
+
+Test obsstore related revsets
+
+  $ hg init repo1
+  $ cd repo1
+  $ cat <<EOF >> .hg/hgrc
+  > [experimental]
+  > evolution = createmarkers
+  > EOF
+
+  $ hg debugdrawdag <<'EOS'
+  >        F G
+  >        |/    # split: B -> E, F
+  > B C D  E     # amend: B -> C -> D
+  >  \|/   |     # amend: F -> G
+  >   A    A  Z  # amend: A -> Z
+  > EOS
+
+  $ hg log -r 'successors(Z)' -T '{desc}\n'
+  Z
+
+  $ hg log -r 'successors(F)' -T '{desc}\n'
+  F
+  G
+
+  $ hg tag --remove --local C D E F G
+
+  $ hg log -r 'successors(B)' -T '{desc}\n'
+  B
+  D
+  E
+  G
+
+  $ hg log -r 'successors(B)' -T '{desc}\n' --hidden
+  B
+  C
+  D
+  E
+  F
+  G
+
+  $ hg log -r 'successors(B)-obsolete()' -T '{desc}\n' --hidden
+  D
+  E
+  G
+
+  $ hg log -r 'successors(B+A)-divergent()' -T '{desc}\n'
+  A
+  Z
+  B
+
+  $ hg log -r 'successors(B+A)-divergent()-obsolete()' -T '{desc}\n'
+  Z