diff mercurial/revset.py @ 34065:c6c8a52e28c9

revset: optimize "draft() & ::x" pattern The `draft() & ::x` type query could be common for selecting one or more draft feature branches being worked on. Before this patch, `::x` may travel through the changelog DAG for a long distance until it gets a smaller revision number than `min(draft())`. It could be very slow on long changelog with distant (in terms of revision numbers) drafts. This patch adds a fast path for this situation, and will stop traveling the changelog DAG once `::x` hits a non-draft revision. The fast path also works for `secret()` and `not public()`. To measure the performance difference, I used drawdag to create a repo that emulates distant drafts: DRAFT4 | DRAFT3 # draft / PUBLIC9999 # public | PUBLIC9998 | . DRAFT2 . | . DRAFT1 # draft | / PUBLIC0001 # public And measured the performance using the repo: (BEFORE) $ hg perfrevset 'draft() & ::(DRAFT2+DRAFT4)' ! wall 0.017132 comb 0.010000 user 0.010000 sys 0.000000 (best of 156) $ hg perfrevset 'draft() & ::(all())' ! wall 0.024221 comb 0.030000 user 0.030000 sys 0.000000 (best of 113) (AFTER) $ hg perfrevset 'draft() & ::(DRAFT2+DRAFT4)' ! wall 0.000243 comb 0.000000 user 0.000000 sys 0.000000 (best of 9303) $ hg perfrevset 'draft() & ::(all())' ! wall 0.004319 comb 0.000000 user 0.000000 sys 0.000000 (best of 655) Differential Revision: https://phab.mercurial-scm.org/D441
author Jun Wu <quark@fb.com>
date Mon, 28 Aug 2017 14:49:00 -0700
parents 37b82485097f
children b0790bebfcf8
line wrap: on
line diff
--- a/mercurial/revset.py	Fri Sep 01 12:13:17 2017 -0700
+++ b/mercurial/revset.py	Mon Aug 28 14:49:00 2017 -0700
@@ -1577,6 +1577,37 @@
     getargs(x, 0, 0, "_notpublic takes no arguments")
     return _phase(repo, subset, phases.draft, phases.secret)
 
+# for internal use
+@predicate('_phaseandancestors(phasename, set)', safe=True)
+def _phaseandancestors(repo, subset, x):
+    # equivalent to (phasename() & ancestors(set)) but more efficient
+    # phasename could be one of 'draft', 'secret', or '_notpublic'
+    args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
+    phasename = getsymbol(args[0])
+    s = getset(repo, fullreposet(repo), args[1])
+
+    draft = phases.draft
+    secret = phases.secret
+    phasenamemap = {
+        '_notpublic': draft,
+        'draft': draft, # follow secret's ancestors
+        'secret': secret,
+    }
+    if phasename not in phasenamemap:
+        raise error.ParseError('%r is not a valid phasename' % phasename)
+
+    minimalphase = phasenamemap[phasename]
+    getphase = repo._phasecache.phase
+
+    def cutfunc(rev):
+        return getphase(repo, rev) < minimalphase
+
+    revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
+
+    if phasename == 'draft': # need to remove secret changesets
+        revs = revs.filter(lambda r: getphase(repo, r) == draft)
+    return subset & revs
+
 @predicate('public()', safe=True)
 def public(repo, subset, x):
     """Changeset in public phase."""