##// END OF EJS Templates
fileset: rewrite predicates to return matcher not closed to subset (API) (BC)...
Yuya Nishihara -
r38948:ff5b6fca default
parent child Browse files
Show More
@@ -362,8 +362,10 b' def lfsfileset(mctx, x):'
362 362 """File that uses LFS storage."""
363 363 # i18n: "lfs" is a keyword
364 364 fileset.getargs(x, 0, 0, _("lfs takes no arguments"))
365 return [f for f in mctx.subset
366 if wrapper.pointerfromctx(mctx.ctx, f, removed=True) is not None]
365 ctx = mctx.ctx
366 def lfsfilep(f):
367 return wrapper.pointerfromctx(ctx, f, removed=True) is not None
368 return mctx.predicate(lfsfilep, predrepr='<lfs>')
367 369
368 370 @templatekeyword('lfs_files', requires={'ctx'})
369 371 def lfsfiles(context, mapping):
@@ -140,43 +140,41 b' def getargs(x, min, max, err):'
140 140 raise error.ParseError(err)
141 141 return l
142 142
143 def getset(mctx, x):
143 def getmatch(mctx, x):
144 144 if not x:
145 145 raise error.ParseError(_("missing argument"))
146 146 return methods[x[0]](mctx, *x[1:])
147 147
148 def stringset(mctx, x):
149 m = mctx.matcher([x])
150 return [f for f in mctx.subset if m(f)]
148 def stringmatch(mctx, x):
149 return mctx.matcher([x])
151 150
152 def kindpatset(mctx, x, y):
153 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
154 _("pattern must be a string")))
151 def kindpatmatch(mctx, x, y):
152 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
153 _("pattern must be a string")))
155 154
156 def andset(mctx, x, y):
157 xl = set(getset(mctx, x))
158 yl = getset(mctx, y)
159 return [f for f in yl if f in xl]
155 def andmatch(mctx, x, y):
156 xm = getmatch(mctx, x)
157 ym = getmatch(mctx, y)
158 return matchmod.intersectmatchers(xm, ym)
160 159
161 def orset(mctx, x, y):
162 # needs optimizing
163 xl = getset(mctx, x)
164 yl = getset(mctx, y)
165 return xl + [f for f in yl if f not in xl]
160 def ormatch(mctx, x, y):
161 xm = getmatch(mctx, x)
162 ym = getmatch(mctx, y)
163 return matchmod.unionmatcher([xm, ym])
166 164
167 def notset(mctx, x):
168 s = set(getset(mctx, x))
169 return [r for r in mctx.subset if r not in s]
165 def notmatch(mctx, x):
166 m = getmatch(mctx, x)
167 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
170 168
171 def minusset(mctx, x, y):
172 xl = getset(mctx, x)
173 yl = set(getset(mctx, y))
174 return [f for f in xl if f not in yl]
169 def minusmatch(mctx, x, y):
170 xm = getmatch(mctx, x)
171 ym = getmatch(mctx, y)
172 return matchmod.differencematcher(xm, ym)
175 173
176 def negateset(mctx, x):
174 def negatematch(mctx, x):
177 175 raise error.ParseError(_("can't use negate operator in this context"))
178 176
179 def listset(mctx, a, b):
177 def listmatch(mctx, x, y):
180 178 raise error.ParseError(_("can't use a list in this context"),
181 179 hint=_('see hg help "filesets.x or y"'))
182 180
@@ -217,7 +215,7 b' def modified(mctx, x):'
217 215 # i18n: "modified" is a keyword
218 216 getargs(x, 0, 0, _("modified takes no arguments"))
219 217 s = set(mctx.status().modified)
220 return [f for f in mctx.subset if f in s]
218 return mctx.predicate(s.__contains__, predrepr='modified')
221 219
222 220 @predicate('added()', callstatus=True)
223 221 def added(mctx, x):
@@ -226,7 +224,7 b' def added(mctx, x):'
226 224 # i18n: "added" is a keyword
227 225 getargs(x, 0, 0, _("added takes no arguments"))
228 226 s = set(mctx.status().added)
229 return [f for f in mctx.subset if f in s]
227 return mctx.predicate(s.__contains__, predrepr='added')
230 228
231 229 @predicate('removed()', callstatus=True)
232 230 def removed(mctx, x):
@@ -235,7 +233,7 b' def removed(mctx, x):'
235 233 # i18n: "removed" is a keyword
236 234 getargs(x, 0, 0, _("removed takes no arguments"))
237 235 s = set(mctx.status().removed)
238 return [f for f in mctx.subset if f in s]
236 return mctx.predicate(s.__contains__, predrepr='removed')
239 237
240 238 @predicate('deleted()', callstatus=True)
241 239 def deleted(mctx, x):
@@ -244,7 +242,7 b' def deleted(mctx, x):'
244 242 # i18n: "deleted" is a keyword
245 243 getargs(x, 0, 0, _("deleted takes no arguments"))
246 244 s = set(mctx.status().deleted)
247 return [f for f in mctx.subset if f in s]
245 return mctx.predicate(s.__contains__, predrepr='deleted')
248 246
249 247 @predicate('missing()', callstatus=True)
250 248 def missing(mctx, x):
@@ -253,27 +251,23 b' def missing(mctx, x):'
253 251 # i18n: "missing" is a keyword
254 252 getargs(x, 0, 0, _("missing takes no arguments"))
255 253 s = set(mctx.status().deleted)
256 return [f for f in mctx.subset if f in s]
254 return mctx.predicate(s.__contains__, predrepr='deleted')
257 255
258 256 @predicate('unknown()', callstatus=True)
259 257 def unknown(mctx, x):
260 """File that is unknown according to :hg:`status`. These files will only be
261 considered if this predicate is used.
262 """
258 """File that is unknown according to :hg:`status`."""
263 259 # i18n: "unknown" is a keyword
264 260 getargs(x, 0, 0, _("unknown takes no arguments"))
265 261 s = set(mctx.status().unknown)
266 return [f for f in mctx.subset if f in s]
262 return mctx.predicate(s.__contains__, predrepr='unknown')
267 263
268 264 @predicate('ignored()', callstatus=True)
269 265 def ignored(mctx, x):
270 """File that is ignored according to :hg:`status`. These files will only be
271 considered if this predicate is used.
272 """
266 """File that is ignored according to :hg:`status`."""
273 267 # i18n: "ignored" is a keyword
274 268 getargs(x, 0, 0, _("ignored takes no arguments"))
275 269 s = set(mctx.status().ignored)
276 return [f for f in mctx.subset if f in s]
270 return mctx.predicate(s.__contains__, predrepr='ignored')
277 271
278 272 @predicate('clean()', callstatus=True)
279 273 def clean(mctx, x):
@@ -282,14 +276,14 b' def clean(mctx, x):'
282 276 # i18n: "clean" is a keyword
283 277 getargs(x, 0, 0, _("clean takes no arguments"))
284 278 s = set(mctx.status().clean)
285 return [f for f in mctx.subset if f in s]
279 return mctx.predicate(s.__contains__, predrepr='clean')
286 280
287 281 @predicate('tracked()')
288 282 def tracked(mctx, x):
289 283 """File that is under Mercurial control."""
290 284 # i18n: "tracked" is a keyword
291 285 getargs(x, 0, 0, _("tracked takes no arguments"))
292 return [f for f in mctx.subset if f in mctx.ctx]
286 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
293 287
294 288 @predicate('binary()', callexisting=True)
295 289 def binary(mctx, x):
@@ -297,7 +291,8 b' def binary(mctx, x):'
297 291 """
298 292 # i18n: "binary" is a keyword
299 293 getargs(x, 0, 0, _("binary takes no arguments"))
300 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
294 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
295 predrepr='binary', cache=True)
301 296
302 297 @predicate('exec()', callexisting=True)
303 298 def exec_(mctx, x):
@@ -305,7 +300,8 b' def exec_(mctx, x):'
305 300 """
306 301 # i18n: "exec" is a keyword
307 302 getargs(x, 0, 0, _("exec takes no arguments"))
308 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
303 ctx = mctx.ctx
304 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
309 305
310 306 @predicate('symlink()', callexisting=True)
311 307 def symlink(mctx, x):
@@ -313,7 +309,8 b' def symlink(mctx, x):'
313 309 """
314 310 # i18n: "symlink" is a keyword
315 311 getargs(x, 0, 0, _("symlink takes no arguments"))
316 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
312 ctx = mctx.ctx
313 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
317 314
318 315 @predicate('resolved()')
319 316 def resolved(mctx, x):
@@ -322,9 +319,10 b' def resolved(mctx, x):'
322 319 # i18n: "resolved" is a keyword
323 320 getargs(x, 0, 0, _("resolved takes no arguments"))
324 321 if mctx.ctx.rev() is not None:
325 return []
322 return mctx.never()
326 323 ms = merge.mergestate.read(mctx.ctx.repo())
327 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
324 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
325 predrepr='resolved')
328 326
329 327 @predicate('unresolved()')
330 328 def unresolved(mctx, x):
@@ -333,9 +331,10 b' def unresolved(mctx, x):'
333 331 # i18n: "unresolved" is a keyword
334 332 getargs(x, 0, 0, _("unresolved takes no arguments"))
335 333 if mctx.ctx.rev() is not None:
336 return []
334 return mctx.never()
337 335 ms = merge.mergestate.read(mctx.ctx.repo())
338 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
336 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
337 predrepr='unresolved')
339 338
340 339 @predicate('hgignore()')
341 340 def hgignore(mctx, x):
@@ -343,8 +342,7 b' def hgignore(mctx, x):'
343 342 """
344 343 # i18n: "hgignore" is a keyword
345 344 getargs(x, 0, 0, _("hgignore takes no arguments"))
346 ignore = mctx.ctx.repo().dirstate._ignore
347 return [f for f in mctx.subset if ignore(f)]
345 return mctx.ctx.repo().dirstate._ignore
348 346
349 347 @predicate('portable()')
350 348 def portable(mctx, x):
@@ -353,8 +351,8 b' def portable(mctx, x):'
353 351 """
354 352 # i18n: "portable" is a keyword
355 353 getargs(x, 0, 0, _("portable takes no arguments"))
356 checkwinfilename = util.checkwinfilename
357 return [f for f in mctx.subset if checkwinfilename(f) is None]
354 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
355 predrepr='portable')
358 356
359 357 @predicate('grep(regex)', callexisting=True)
360 358 def grep(mctx, x):
@@ -366,7 +364,8 b' def grep(mctx, x):'
366 364 except re.error as e:
367 365 raise error.ParseError(_('invalid match pattern: %s') %
368 366 stringutil.forcebytestr(e))
369 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
367 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
368 predrepr=('grep(%r)', r.pattern), cache=True)
370 369
371 370 def _sizetomax(s):
372 371 try:
@@ -421,7 +420,8 b' def size(mctx, x):'
421 420 # i18n: "size" is a keyword
422 421 expr = getstring(x, _("size requires an expression"))
423 422 m = sizematcher(expr)
424 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
423 return mctx.fpredicate(lambda fctx: m(fctx.size()),
424 predrepr=('size(%r)', expr), cache=True)
425 425
426 426 @predicate('encoding(name)', callexisting=True)
427 427 def encoding(mctx, x):
@@ -433,18 +433,17 b' def encoding(mctx, x):'
433 433 # i18n: "encoding" is a keyword
434 434 enc = getstring(x, _("encoding requires an encoding name"))
435 435
436 s = []
437 for f in mctx.existing():
438 d = mctx.ctx[f].data()
436 def encp(fctx):
437 d = fctx.data()
439 438 try:
440 439 d.decode(pycompat.sysstr(enc))
440 return True
441 441 except LookupError:
442 442 raise error.Abort(_("unknown encoding '%s'") % enc)
443 443 except UnicodeDecodeError:
444 continue
445 s.append(f)
444 return False
446 445
447 return s
446 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
448 447
449 448 @predicate('eol(style)', callexisting=True)
450 449 def eol(mctx, x):
@@ -456,19 +455,18 b' def eol(mctx, x):'
456 455 # i18n: "eol" is a keyword
457 456 enc = getstring(x, _("eol requires a style name"))
458 457
459 s = []
460 for f in mctx.existing():
461 fctx = mctx.ctx[f]
458 def eolp(fctx):
462 459 if fctx.isbinary():
463 continue
460 return False
464 461 d = fctx.data()
465 462 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
466 s.append(f)
463 return True
467 464 elif enc == 'unix' and re.search('(?<!\r)\n', d):
468 s.append(f)
465 return True
469 466 elif enc == 'mac' and re.search('\r(?!\n)', d):
470 s.append(f)
471 return s
467 return True
468 return False
469 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
472 470
473 471 @predicate('copied()')
474 472 def copied(mctx, x):
@@ -476,13 +474,10 b' def copied(mctx, x):'
476 474 """
477 475 # i18n: "copied" is a keyword
478 476 getargs(x, 0, 0, _("copied takes no arguments"))
479 s = []
480 for f in mctx.subset:
481 if f in mctx.ctx:
482 p = mctx.ctx[f].parents()
483 if p and p[0].path() != f:
484 s.append(f)
485 return s
477 def copiedp(fctx):
478 p = fctx.parents()
479 return p and p[0].path() != fctx.path()
480 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
486 481
487 482 @predicate('revs(revs, pattern)')
488 483 def revs(mctx, x):
@@ -496,15 +491,15 b' def revs(mctx, x):'
496 491 repo = mctx.ctx.repo()
497 492 revs = scmutil.revrange(repo, [revspec])
498 493
499 found = set()
500 result = []
494 matchers = []
501 495 for r in revs:
502 496 ctx = repo[r]
503 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
504 if f not in found:
505 found.add(f)
506 result.append(f)
507 return result
497 matchers.append(getmatch(mctx.switch(ctx, _buildstatus(ctx, x)), x))
498 if not matchers:
499 return mctx.never()
500 if len(matchers) == 1:
501 return matchers[0]
502 return matchmod.unionmatcher(matchers)
508 503
509 504 @predicate('status(base, rev, pattern)')
510 505 def status(mctx, x):
@@ -526,7 +521,7 b' def status(mctx, x):'
526 521 if not revspec:
527 522 raise error.ParseError(reverr)
528 523 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
529 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
524 return getmatch(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
530 525
531 526 @predicate('subrepo([pattern])')
532 527 def subrepo(mctx, x):
@@ -535,7 +530,7 b' def subrepo(mctx, x):'
535 530 # i18n: "subrepo" is a keyword
536 531 getargs(x, 0, 1, _("subrepo takes at most one argument"))
537 532 ctx = mctx.ctx
538 sstate = sorted(ctx.substate)
533 sstate = ctx.substate
539 534 if x:
540 535 pat = getpattern(x, matchmod.allpatternkinds,
541 536 # i18n: "subrepo" is a keyword
@@ -546,21 +541,22 b' def subrepo(mctx, x):'
546 541 return (s == pat)
547 542 else:
548 543 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
549 return [sub for sub in sstate if m(sub)]
544 return mctx.predicate(lambda f: f in sstate and m(f),
545 predrepr=('subrepo(%r)', pat))
550 546 else:
551 return [sub for sub in sstate]
547 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
552 548
553 549 methods = {
554 'string': stringset,
555 'symbol': stringset,
556 'kindpat': kindpatset,
557 'and': andset,
558 'or': orset,
559 'minus': minusset,
560 'negate': negateset,
561 'list': listset,
562 'group': getset,
563 'not': notset,
550 'string': stringmatch,
551 'symbol': stringmatch,
552 'kindpat': kindpatmatch,
553 'and': andmatch,
554 'or': ormatch,
555 'minus': minusmatch,
556 'negate': negatematch,
557 'list': listmatch,
558 'group': getmatch,
559 'not': notmatch,
564 560 'func': func,
565 561 }
566 562
@@ -680,12 +676,9 b' def _buildsubset(ctx, status):'
680 676
681 677 def match(ctx, expr, badfn=None):
682 678 """Create a matcher for a single fileset expression"""
683 repo = ctx.repo()
684 679 tree = parse(expr)
685 fset = getset(fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn), tree)
686 return matchmod.predicatematcher(repo.root, repo.getcwd(),
687 fset.__contains__,
688 predrepr='fileset', badfn=badfn)
680 mctx = fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
681 return getmatch(mctx, tree)
689 682
690 683 def _buildstatus(ctx, tree, basectx=None):
691 684 # do we need status info?
@@ -142,8 +142,10 b' Test files status'
142 142 .hgignore
143 143 c2
144 144 $ fileset 'hgignore()'
145 .hgignore
145 146 a2
146 147 b2
148 c2
147 149 $ fileset 'clean()'
148 150 b1
149 151 $ fileset 'copied()'
@@ -182,6 +184,7 b' Test files properties'
182 184
183 185 >>> open('bin', 'wb').write(b'\0a') and None
184 186 $ fileset 'binary()'
187 bin
185 188 $ fileset 'binary() and unknown()'
186 189 bin
187 190 $ echo '^bin$' >> .hgignore
@@ -192,6 +195,7 b' Test files properties'
192 195 bin
193 196
194 197 $ fileset 'grep("b{1}")'
198 .hgignore
195 199 b1
196 200 b2
197 201 c1
@@ -354,8 +358,12 b' Test with a revision'
354 358 $ fileset -r1 'unknown()'
355 359 $ fileset -r1 'ignored()'
356 360 $ fileset -r1 'hgignore()'
361 .hgignore
362 a2
357 363 b2
358 364 bin
365 c2
366 sub2
359 367 $ fileset -r1 'binary()'
360 368 bin
361 369 $ fileset -r1 'size(1k)'
@@ -403,30 +411,42 b' Test with a revision'
403 411 dos
404 412 mixed
405 413 $ fileset 'eol(unix)'
414 .hgignore
406 415 .hgsub
407 416 .hgsubstate
408 417 b1
409 418 b2
419 b2.orig
410 420 c1
421 c2
422 c3
423 con.xml
411 424 mixed
425 unknown
412 426 $ fileset 'eol(mac)'
413 427 mac
414 428
415 429 Test safety of 'encoding' on removed files
416 430
417 431 $ fileset 'encoding("ascii")'
432 .hgignore
418 433 .hgsub
419 434 .hgsubstate
420 435 1k
421 436 2k
422 437 b1
423 438 b2
439 b2.orig
424 440 b2link (symlink !)
425 441 bin
426 442 c1
443 c2
444 c3
445 con.xml
427 446 dos
428 447 mac
429 448 mixed
449 unknown
430 450
431 451 Test detection of unintentional 'matchctx.existing()' invocation
432 452
@@ -437,7 +457,8 b" Test detection of unintentional 'matchct"
437 457 > @filesetpredicate(b'existingcaller()', callexisting=False)
438 458 > def existingcaller(mctx, x):
439 459 > # this 'mctx.existing()' invocation is unintentional
440 > return [f for f in mctx.existing()]
460 > existing = set(mctx.existing())
461 > return mctx.predicate(existing.__contains__, cache=False)
441 462 > EOF
442 463
443 464 $ cat >> .hg/hgrc <<EOF
@@ -523,8 +523,8 b" absence doesn't cause an abort.)"
523 523
524 524 $ hg files --debug -r . 'set:eol("unix")' --config 'experimental.lfs.disableusercache=True'
525 525 lfs: found c04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b7adeeafb5474c4adae9faa80 in the local lfs store
526 2 b
526 527 lfs: found 5dde896887f6754c9b15bfe3a441ae4806df2fde94001311e08bf110622e0bbe in the local lfs store
527 2 b
528 528
529 529 $ hg files --debug -r . 'set:binary()' --config 'experimental.lfs.disableusercache=True'
530 530 2 a
General Comments 0
You need to be logged in to leave comments. Login now