# HG changeset patch # User FUJIWARA Katsunori # Date 1450704676 -32400 # Node ID c39ecb2b86b36ccd1f45d2373dd161200c5ee1a8 # Parent a8afdc5a78850adce4b1944108b086e132bfb977 fileset: detect unintentional existing() invocation at runtime A fileset predicate can invoke 'matchctx.existing()' successfully, even if it isn't marked as "existing caller". It is aborted only in some corner cases: e.g. there were one deleted file in the working directory (see 8a0513bf030a for detail). This patch makes 'matchctx.existing()' invocation abort if not '_existingenabled', which is true only while "existing caller" running. After this changes, non-"existing caller" predicate function is aborted immediately, whenever it invokes 'matchctx.existing()'. This prevent developer from forgetting to mark a predicate as "existing caller". BTW, unintentional 'matchctx.status()' invocation can be detected easily without any additional trick like this patch, because it returns 'None' if a predicate isn't marked as "status caller", and referring field (e.g. '.modified') of it is always aborted. diff -r a8afdc5a7885 -r c39ecb2b86b3 mercurial/fileset.py --- a/mercurial/fileset.py Mon Dec 21 22:31:16 2015 +0900 +++ b/mercurial/fileset.py Mon Dec 21 22:31:16 2015 +0900 @@ -248,7 +248,13 @@ def func(mctx, a, b): if a[0] == 'symbol' and a[1] in symbols: - return symbols[a[1]](mctx, b) + funcname = a[1] + enabled = mctx._existingenabled + mctx._existingenabled = funcname in _existingcallers + try: + return symbols[funcname](mctx, b) + finally: + mctx._existingenabled = enabled keep = lambda fn: getattr(fn, '__doc__', None) is not None @@ -497,6 +503,7 @@ self.ctx = ctx self.subset = subset self._status = status + self._existingenabled = False def status(self): return self._status def matcher(self, patterns): @@ -504,6 +511,7 @@ def filter(self, files): return [f for f in files if f in self.subset] def existing(self): + assert self._existingenabled, 'unexpected existing() invocation' if self._status is not None: removed = set(self._status[3]) unknown = set(self._status[4] + self._status[5]) diff -r a8afdc5a7885 -r c39ecb2b86b3 tests/test-fileset.t --- a/tests/test-fileset.t Mon Dec 21 22:31:16 2015 +0900 +++ b/tests/test-fileset.t Mon Dec 21 22:31:16 2015 +0900 @@ -328,3 +328,22 @@ b2link bin c1 + +Test detection of unintentional 'matchctx.existing()' invocation + + $ cat > $TESTTMP/existingcaller.py < from mercurial import fileset + > + > @fileset.predicate('existingcaller()', callexisting=False) + > def existingcaller(mctx, x): + > # this 'mctx.existing()' invocation is unintentional + > return [f for f in mctx.existing()] + > EOF + + $ cat >> .hg/hgrc < [extensions] + > existingcaller = $TESTTMP/existingcaller.py + > EOF + + $ fileset 'existingcaller()' 2>&1 | tail -1 + AssertionError: unexpected existing() invocation