diff mercurial/filesetlang.py @ 38918:e79a69af1593

fileset: insert hints where status should be computed This will allow us to compute status against a narrowed set of files. For example, "path:build/ & (unknown() + missing())" is rewritten as "path:build/ & <withstatus>(unknown() + missing(), 'unknown missing')", and the status call can be narrowed by the left-hand-side matcher, "path:build/". mctx.buildstatus() calls will be solely processed by getmatchwithstatus().
author Yuya Nishihara <yuya@tcha.org>
date Sat, 21 Jul 2018 20:27:53 +0900
parents 899b4c74209c
children 2372284d9457
line wrap: on
line diff
--- a/mercurial/filesetlang.py	Sun Jul 22 11:12:55 2018 +0900
+++ b/mercurial/filesetlang.py	Sat Jul 21 20:27:53 2018 +0900
@@ -171,6 +171,82 @@
         return (op, x[1], ta)
     raise error.ProgrammingError('invalid operator %r' % op)
 
+def _insertstatushints(x):
+    """Insert hint nodes where status should be calculated (first path)
+
+    This works in bottom-up way, summing up status names and inserting hint
+    nodes at 'and' and 'or' as needed. Thus redundant hint nodes may be left.
+
+    Returns (status-names, new-tree) at the given subtree, where status-names
+    is a sum of status names referenced in the given subtree.
+    """
+    if x is None:
+        return (), x
+
+    op = x[0]
+    if op in {'string', 'symbol', 'kindpat'}:
+        return (), x
+    if op == 'not':
+        h, t = _insertstatushints(x[1])
+        return h, (op, t)
+    if op == 'and':
+        ha, ta = _insertstatushints(x[1])
+        hb, tb = _insertstatushints(x[2])
+        hr = ha + hb
+        if ha and hb:
+            return hr, ('withstatus', (op, ta, tb), ('string', ' '.join(hr)))
+        return hr, (op, ta, tb)
+    if op == 'or':
+        hs, ts = zip(*(_insertstatushints(y) for y in x[1:]))
+        hr = sum(hs, ())
+        if sum(bool(h) for h in hs) > 1:
+            return hr, ('withstatus', (op,) + ts, ('string', ' '.join(hr)))
+        return hr, (op,) + ts
+    if op == 'list':
+        hs, ts = zip(*(_insertstatushints(y) for y in x[1:]))
+        return sum(hs, ()), (op,) + ts
+    if op == 'func':
+        f = getsymbol(x[1])
+        # don't propagate 'ha' crossing a function boundary
+        ha, ta = _insertstatushints(x[2])
+        if getattr(symbols.get(f), '_callstatus', False):
+            return (f,), ('withstatus', (op, x[1], ta), ('string', f))
+        return (), (op, x[1], ta)
+    raise error.ProgrammingError('invalid operator %r' % op)
+
+def _mergestatushints(x, instatus):
+    """Remove redundant status hint nodes (second path)
+
+    This is the top-down path to eliminate inner hint nodes.
+    """
+    if x is None:
+        return x
+
+    op = x[0]
+    if op == 'withstatus':
+        if instatus:
+            # drop redundant hint node
+            return _mergestatushints(x[1], instatus)
+        t = _mergestatushints(x[1], instatus=True)
+        return (op, t, x[2])
+    if op in {'string', 'symbol', 'kindpat'}:
+        return x
+    if op == 'not':
+        t = _mergestatushints(x[1], instatus)
+        return (op, t)
+    if op == 'and':
+        ta = _mergestatushints(x[1], instatus)
+        tb = _mergestatushints(x[2], instatus)
+        return (op, ta, tb)
+    if op in {'list', 'or'}:
+        ts = tuple(_mergestatushints(y, instatus) for y in x[1:])
+        return (op,) + ts
+    if op == 'func':
+        # don't propagate 'instatus' crossing a function boundary
+        ta = _mergestatushints(x[2], instatus=False)
+        return (op, x[1], ta)
+    raise error.ProgrammingError('invalid operator %r' % op)
+
 def analyze(x):
     """Transform raw parsed tree to evaluatable tree which can be fed to
     optimize() or getmatch()
@@ -178,7 +254,9 @@
     All pseudo operations should be mapped to real operations or functions
     defined in methods or symbols table respectively.
     """
-    return _analyze(x)
+    t = _analyze(x)
+    _h, t = _insertstatushints(t)
+    return _mergestatushints(t, instatus=False)
 
 def _optimizeandops(op, ta, tb):
     if tb is not None and tb[0] == 'not':
@@ -205,6 +283,9 @@
         return 0, x
 
     op = x[0]
+    if op == 'withstatus':
+        w, t = _optimize(x[1])
+        return w, (op, t, x[2])
     if op in {'string', 'symbol'}:
         return WEIGHT_CHECK_FILENAME, x
     if op == 'kindpat':