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().
--- a/mercurial/fileset.py Sun Jul 22 11:12:55 2018 +0900
+++ b/mercurial/fileset.py Sat Jul 21 20:27:53 2018 +0900
@@ -43,6 +43,9 @@
raise error.ParseError(_("missing argument"))
return methods[x[0]](mctx, *x[1:])
+def getmatchwithstatus(mctx, x, hint):
+ return getmatch(mctx, x)
+
def stringmatch(mctx, x):
return mctx.matcher([x])
@@ -443,6 +446,7 @@
return mctx.predicate(sstate.__contains__, predrepr='subrepo')
methods = {
+ 'withstatus': getmatchwithstatus,
'string': stringmatch,
'symbol': stringmatch,
'kindpat': kindpatmatch,
--- 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':
--- a/mercurial/minifileset.py Sun Jul 22 11:12:55 2018 +0900
+++ b/mercurial/minifileset.py Sat Jul 21 20:27:53 2018 +0900
@@ -24,7 +24,9 @@
if not tree:
raise error.ParseError(_("missing argument"))
op = tree[0]
- if op in {'symbol', 'string', 'kindpat'}:
+ if op == 'withstatus':
+ return _compile(tree[1])
+ elif op in {'symbol', 'string', 'kindpat'}:
name = filesetlang.getpattern(tree, {'path'}, _('invalid file pattern'))
if name.startswith('**'): # file extension test, ex. "**.tar.gz"
ext = name[2:]
--- a/tests/test-fileset.t Sun Jul 22 11:12:55 2018 +0900
+++ b/tests/test-fileset.t Sat Jul 21 20:27:53 2018 +0900
@@ -175,18 +175,22 @@
(func
(symbol 'grep')
(string 'b'))
- (func
- (symbol 'clean')
- None)))
+ (withstatus
+ (func
+ (symbol 'clean')
+ None)
+ (string 'clean'))))
* optimized:
(or
(patterns
(symbol 'a1')
(symbol 'a2'))
(and
- (func
- (symbol 'clean')
- None)
+ (withstatus
+ (func
+ (symbol 'clean')
+ None)
+ (string 'clean'))
(func
(symbol 'grep')
(string 'b'))))
@@ -374,6 +378,156 @@
b2
c1
+Test insertion of status hints
+
+ $ fileset -p optimized 'added()'
+ * optimized:
+ (withstatus
+ (func
+ (symbol 'added')
+ None)
+ (string 'added'))
+ c1
+
+ $ fileset -p optimized 'a* & removed()'
+ * optimized:
+ (and
+ (symbol 'a*')
+ (withstatus
+ (func
+ (symbol 'removed')
+ None)
+ (string 'removed')))
+ a2
+
+ $ fileset -p optimized 'a* - removed()'
+ * optimized:
+ (minus
+ (symbol 'a*')
+ (withstatus
+ (func
+ (symbol 'removed')
+ None)
+ (string 'removed')))
+ a1
+
+ $ fileset -p analyzed -p optimized '(added() + removed()) - a*'
+ * analyzed:
+ (and
+ (withstatus
+ (or
+ (func
+ (symbol 'added')
+ None)
+ (func
+ (symbol 'removed')
+ None))
+ (string 'added removed'))
+ (not
+ (symbol 'a*')))
+ * optimized:
+ (and
+ (not
+ (symbol 'a*'))
+ (withstatus
+ (or
+ (func
+ (symbol 'added')
+ None)
+ (func
+ (symbol 'removed')
+ None))
+ (string 'added removed')))
+ c1
+
+ $ fileset -p optimized 'a* + b* + added() + unknown()'
+ * optimized:
+ (withstatus
+ (or
+ (patterns
+ (symbol 'a*')
+ (symbol 'b*'))
+ (func
+ (symbol 'added')
+ None)
+ (func
+ (symbol 'unknown')
+ None))
+ (string 'added unknown'))
+ a1
+ a2
+ b1
+ b2
+ c1
+ c3
+
+ $ fileset -p analyzed -p optimized 'removed() & missing() & a*'
+ * analyzed:
+ (and
+ (withstatus
+ (and
+ (func
+ (symbol 'removed')
+ None)
+ (func
+ (symbol 'missing')
+ None))
+ (string 'removed missing'))
+ (symbol 'a*'))
+ * optimized:
+ (and
+ (symbol 'a*')
+ (withstatus
+ (and
+ (func
+ (symbol 'removed')
+ None)
+ (func
+ (symbol 'missing')
+ None))
+ (string 'removed missing')))
+
+ $ fileset -p optimized 'clean() & revs(0, added())'
+ * optimized:
+ (and
+ (withstatus
+ (func
+ (symbol 'clean')
+ None)
+ (string 'clean'))
+ (func
+ (symbol 'revs')
+ (list
+ (symbol '0')
+ (withstatus
+ (func
+ (symbol 'added')
+ None)
+ (string 'added')))))
+ b1
+
+ $ fileset -p optimized 'clean() & status(null, 0, b* & added())'
+ * optimized:
+ (and
+ (withstatus
+ (func
+ (symbol 'clean')
+ None)
+ (string 'clean'))
+ (func
+ (symbol 'status')
+ (list
+ (symbol 'null')
+ (symbol '0')
+ (and
+ (symbol 'b*')
+ (withstatus
+ (func
+ (symbol 'added')
+ None)
+ (string 'added'))))))
+ b1
+
Test files properties
>>> open('bin', 'wb').write(b'\0a') and None