[PATCH 4 of 7] fileset: insert hints where status should be computed
Yuya Nishihara
yuya at tcha.org
Sun Aug 5 10:31:24 EDT 2018
# HG changeset patch
# User Yuya Nishihara <yuya at tcha.org>
# Date 1532172473 -32400
# Sat Jul 21 20:27:53 2018 +0900
# Node ID 3d3578872b9430d0e6681f1fae09c6f2901b1362
# Parent 8cec7522133a5d5a7b4f6d04fd3a729dcfaac67a
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().
diff --git a/mercurial/fileset.py b/mercurial/fileset.py
--- a/mercurial/fileset.py
+++ b/mercurial/fileset.py
@@ -43,6 +43,9 @@ def getmatch(mctx, x):
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 @@ def subrepo(mctx, x):
return mctx.predicate(sstate.__contains__, predrepr='subrepo')
methods = {
+ 'withstatus': getmatchwithstatus,
'string': stringmatch,
'symbol': stringmatch,
'kindpat': kindpatmatch,
diff --git a/mercurial/filesetlang.py b/mercurial/filesetlang.py
--- a/mercurial/filesetlang.py
+++ b/mercurial/filesetlang.py
@@ -171,6 +171,82 @@ def _analyze(x):
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 @@ def analyze(x):
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 @@ def _optimize(x):
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':
diff --git a/mercurial/minifileset.py b/mercurial/minifileset.py
--- a/mercurial/minifileset.py
+++ b/mercurial/minifileset.py
@@ -24,7 +24,9 @@ def _compile(tree):
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:]
diff --git a/tests/test-fileset.t b/tests/test-fileset.t
--- a/tests/test-fileset.t
+++ b/tests/test-fileset.t
@@ -175,18 +175,22 @@ Show parsed tree at stages:
(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 @@ Test files status in different revisions
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
More information about the Mercurial-devel
mailing list