comparison mercurial/fileset.py @ 38689:ff5b6fca1082

fileset: rewrite predicates to return matcher not closed to subset (API) (BC) This makes fileset expression open to any input, so that we can just say "hg status 'set: not binary()'" to select text files including unknowns. With this and removal of subset computation, 'set:**' becomes as fast as 'glob:**'. Further optimization will probably be possible by narrowing the file tree to compute status for example. This also fixes 'subrepo()' to not ignore the current mctx.subset. .. bc:: The fileset expression may include untracked files by default. Use ``tracked()`` to explicitly filter out files not existing at the context revision.
author Yuya Nishihara <yuya@tcha.org>
date Sat, 09 Jun 2018 19:55:10 +0900
parents 1500cbe22d53
children 5d9749c598f0
comparison
equal deleted inserted replaced
38688:2570dca0f21c 38689:ff5b6fca1082
138 l = getlist(x) 138 l = getlist(x)
139 if len(l) < min or len(l) > max: 139 if len(l) < min or len(l) > max:
140 raise error.ParseError(err) 140 raise error.ParseError(err)
141 return l 141 return l
142 142
143 def getset(mctx, x): 143 def getmatch(mctx, x):
144 if not x: 144 if not x:
145 raise error.ParseError(_("missing argument")) 145 raise error.ParseError(_("missing argument"))
146 return methods[x[0]](mctx, *x[1:]) 146 return methods[x[0]](mctx, *x[1:])
147 147
148 def stringset(mctx, x): 148 def stringmatch(mctx, x):
149 m = mctx.matcher([x]) 149 return mctx.matcher([x])
150 return [f for f in mctx.subset if m(f)] 150
151 151 def kindpatmatch(mctx, x, y):
152 def kindpatset(mctx, x, y): 152 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
153 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds, 153 _("pattern must be a string")))
154 _("pattern must be a string"))) 154
155 155 def andmatch(mctx, x, y):
156 def andset(mctx, x, y): 156 xm = getmatch(mctx, x)
157 xl = set(getset(mctx, x)) 157 ym = getmatch(mctx, y)
158 yl = getset(mctx, y) 158 return matchmod.intersectmatchers(xm, ym)
159 return [f for f in yl if f in xl] 159
160 160 def ormatch(mctx, x, y):
161 def orset(mctx, x, y): 161 xm = getmatch(mctx, x)
162 # needs optimizing 162 ym = getmatch(mctx, y)
163 xl = getset(mctx, x) 163 return matchmod.unionmatcher([xm, ym])
164 yl = getset(mctx, y) 164
165 return xl + [f for f in yl if f not in xl] 165 def notmatch(mctx, x):
166 166 m = getmatch(mctx, x)
167 def notset(mctx, x): 167 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
168 s = set(getset(mctx, x)) 168
169 return [r for r in mctx.subset if r not in s] 169 def minusmatch(mctx, x, y):
170 170 xm = getmatch(mctx, x)
171 def minusset(mctx, x, y): 171 ym = getmatch(mctx, y)
172 xl = getset(mctx, x) 172 return matchmod.differencematcher(xm, ym)
173 yl = set(getset(mctx, y)) 173
174 return [f for f in xl if f not in yl] 174 def negatematch(mctx, x):
175
176 def negateset(mctx, x):
177 raise error.ParseError(_("can't use negate operator in this context")) 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 raise error.ParseError(_("can't use a list in this context"), 178 raise error.ParseError(_("can't use a list in this context"),
181 hint=_('see hg help "filesets.x or y"')) 179 hint=_('see hg help "filesets.x or y"'))
182 180
183 def func(mctx, a, b): 181 def func(mctx, a, b):
184 funcname = getsymbol(a) 182 funcname = getsymbol(a)
215 """File that is modified according to :hg:`status`. 213 """File that is modified according to :hg:`status`.
216 """ 214 """
217 # i18n: "modified" is a keyword 215 # i18n: "modified" is a keyword
218 getargs(x, 0, 0, _("modified takes no arguments")) 216 getargs(x, 0, 0, _("modified takes no arguments"))
219 s = set(mctx.status().modified) 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 @predicate('added()', callstatus=True) 220 @predicate('added()', callstatus=True)
223 def added(mctx, x): 221 def added(mctx, x):
224 """File that is added according to :hg:`status`. 222 """File that is added according to :hg:`status`.
225 """ 223 """
226 # i18n: "added" is a keyword 224 # i18n: "added" is a keyword
227 getargs(x, 0, 0, _("added takes no arguments")) 225 getargs(x, 0, 0, _("added takes no arguments"))
228 s = set(mctx.status().added) 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 @predicate('removed()', callstatus=True) 229 @predicate('removed()', callstatus=True)
232 def removed(mctx, x): 230 def removed(mctx, x):
233 """File that is removed according to :hg:`status`. 231 """File that is removed according to :hg:`status`.
234 """ 232 """
235 # i18n: "removed" is a keyword 233 # i18n: "removed" is a keyword
236 getargs(x, 0, 0, _("removed takes no arguments")) 234 getargs(x, 0, 0, _("removed takes no arguments"))
237 s = set(mctx.status().removed) 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 @predicate('deleted()', callstatus=True) 238 @predicate('deleted()', callstatus=True)
241 def deleted(mctx, x): 239 def deleted(mctx, x):
242 """Alias for ``missing()``. 240 """Alias for ``missing()``.
243 """ 241 """
244 # i18n: "deleted" is a keyword 242 # i18n: "deleted" is a keyword
245 getargs(x, 0, 0, _("deleted takes no arguments")) 243 getargs(x, 0, 0, _("deleted takes no arguments"))
246 s = set(mctx.status().deleted) 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 @predicate('missing()', callstatus=True) 247 @predicate('missing()', callstatus=True)
250 def missing(mctx, x): 248 def missing(mctx, x):
251 """File that is missing according to :hg:`status`. 249 """File that is missing according to :hg:`status`.
252 """ 250 """
253 # i18n: "missing" is a keyword 251 # i18n: "missing" is a keyword
254 getargs(x, 0, 0, _("missing takes no arguments")) 252 getargs(x, 0, 0, _("missing takes no arguments"))
255 s = set(mctx.status().deleted) 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 @predicate('unknown()', callstatus=True) 256 @predicate('unknown()', callstatus=True)
259 def unknown(mctx, x): 257 def unknown(mctx, x):
260 """File that is unknown according to :hg:`status`. These files will only be 258 """File that is unknown according to :hg:`status`."""
261 considered if this predicate is used.
262 """
263 # i18n: "unknown" is a keyword 259 # i18n: "unknown" is a keyword
264 getargs(x, 0, 0, _("unknown takes no arguments")) 260 getargs(x, 0, 0, _("unknown takes no arguments"))
265 s = set(mctx.status().unknown) 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 @predicate('ignored()', callstatus=True) 264 @predicate('ignored()', callstatus=True)
269 def ignored(mctx, x): 265 def ignored(mctx, x):
270 """File that is ignored according to :hg:`status`. These files will only be 266 """File that is ignored according to :hg:`status`."""
271 considered if this predicate is used.
272 """
273 # i18n: "ignored" is a keyword 267 # i18n: "ignored" is a keyword
274 getargs(x, 0, 0, _("ignored takes no arguments")) 268 getargs(x, 0, 0, _("ignored takes no arguments"))
275 s = set(mctx.status().ignored) 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 @predicate('clean()', callstatus=True) 272 @predicate('clean()', callstatus=True)
279 def clean(mctx, x): 273 def clean(mctx, x):
280 """File that is clean according to :hg:`status`. 274 """File that is clean according to :hg:`status`.
281 """ 275 """
282 # i18n: "clean" is a keyword 276 # i18n: "clean" is a keyword
283 getargs(x, 0, 0, _("clean takes no arguments")) 277 getargs(x, 0, 0, _("clean takes no arguments"))
284 s = set(mctx.status().clean) 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 @predicate('tracked()') 281 @predicate('tracked()')
288 def tracked(mctx, x): 282 def tracked(mctx, x):
289 """File that is under Mercurial control.""" 283 """File that is under Mercurial control."""
290 # i18n: "tracked" is a keyword 284 # i18n: "tracked" is a keyword
291 getargs(x, 0, 0, _("tracked takes no arguments")) 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 @predicate('binary()', callexisting=True) 288 @predicate('binary()', callexisting=True)
295 def binary(mctx, x): 289 def binary(mctx, x):
296 """File that appears to be binary (contains NUL bytes). 290 """File that appears to be binary (contains NUL bytes).
297 """ 291 """
298 # i18n: "binary" is a keyword 292 # i18n: "binary" is a keyword
299 getargs(x, 0, 0, _("binary takes no arguments")) 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 @predicate('exec()', callexisting=True) 297 @predicate('exec()', callexisting=True)
303 def exec_(mctx, x): 298 def exec_(mctx, x):
304 """File that is marked as executable. 299 """File that is marked as executable.
305 """ 300 """
306 # i18n: "exec" is a keyword 301 # i18n: "exec" is a keyword
307 getargs(x, 0, 0, _("exec takes no arguments")) 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 @predicate('symlink()', callexisting=True) 306 @predicate('symlink()', callexisting=True)
311 def symlink(mctx, x): 307 def symlink(mctx, x):
312 """File that is marked as a symlink. 308 """File that is marked as a symlink.
313 """ 309 """
314 # i18n: "symlink" is a keyword 310 # i18n: "symlink" is a keyword
315 getargs(x, 0, 0, _("symlink takes no arguments")) 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 @predicate('resolved()') 315 @predicate('resolved()')
319 def resolved(mctx, x): 316 def resolved(mctx, x):
320 """File that is marked resolved according to :hg:`resolve -l`. 317 """File that is marked resolved according to :hg:`resolve -l`.
321 """ 318 """
322 # i18n: "resolved" is a keyword 319 # i18n: "resolved" is a keyword
323 getargs(x, 0, 0, _("resolved takes no arguments")) 320 getargs(x, 0, 0, _("resolved takes no arguments"))
324 if mctx.ctx.rev() is not None: 321 if mctx.ctx.rev() is not None:
325 return [] 322 return mctx.never()
326 ms = merge.mergestate.read(mctx.ctx.repo()) 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 @predicate('unresolved()') 327 @predicate('unresolved()')
330 def unresolved(mctx, x): 328 def unresolved(mctx, x):
331 """File that is marked unresolved according to :hg:`resolve -l`. 329 """File that is marked unresolved according to :hg:`resolve -l`.
332 """ 330 """
333 # i18n: "unresolved" is a keyword 331 # i18n: "unresolved" is a keyword
334 getargs(x, 0, 0, _("unresolved takes no arguments")) 332 getargs(x, 0, 0, _("unresolved takes no arguments"))
335 if mctx.ctx.rev() is not None: 333 if mctx.ctx.rev() is not None:
336 return [] 334 return mctx.never()
337 ms = merge.mergestate.read(mctx.ctx.repo()) 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 @predicate('hgignore()') 339 @predicate('hgignore()')
341 def hgignore(mctx, x): 340 def hgignore(mctx, x):
342 """File that matches the active .hgignore pattern. 341 """File that matches the active .hgignore pattern.
343 """ 342 """
344 # i18n: "hgignore" is a keyword 343 # i18n: "hgignore" is a keyword
345 getargs(x, 0, 0, _("hgignore takes no arguments")) 344 getargs(x, 0, 0, _("hgignore takes no arguments"))
346 ignore = mctx.ctx.repo().dirstate._ignore 345 return mctx.ctx.repo().dirstate._ignore
347 return [f for f in mctx.subset if ignore(f)]
348 346
349 @predicate('portable()') 347 @predicate('portable()')
350 def portable(mctx, x): 348 def portable(mctx, x):
351 """File that has a portable name. (This doesn't include filenames with case 349 """File that has a portable name. (This doesn't include filenames with case
352 collisions.) 350 collisions.)
353 """ 351 """
354 # i18n: "portable" is a keyword 352 # i18n: "portable" is a keyword
355 getargs(x, 0, 0, _("portable takes no arguments")) 353 getargs(x, 0, 0, _("portable takes no arguments"))
356 checkwinfilename = util.checkwinfilename 354 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
357 return [f for f in mctx.subset if checkwinfilename(f) is None] 355 predrepr='portable')
358 356
359 @predicate('grep(regex)', callexisting=True) 357 @predicate('grep(regex)', callexisting=True)
360 def grep(mctx, x): 358 def grep(mctx, x):
361 """File contains the given regular expression. 359 """File contains the given regular expression.
362 """ 360 """
364 # i18n: "grep" is a keyword 362 # i18n: "grep" is a keyword
365 r = re.compile(getstring(x, _("grep requires a pattern"))) 363 r = re.compile(getstring(x, _("grep requires a pattern")))
366 except re.error as e: 364 except re.error as e:
367 raise error.ParseError(_('invalid match pattern: %s') % 365 raise error.ParseError(_('invalid match pattern: %s') %
368 stringutil.forcebytestr(e)) 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 def _sizetomax(s): 370 def _sizetomax(s):
372 try: 371 try:
373 s = s.strip().lower() 372 s = s.strip().lower()
374 for k, v in util._sizeunits: 373 for k, v in util._sizeunits:
419 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes 418 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
420 """ 419 """
421 # i18n: "size" is a keyword 420 # i18n: "size" is a keyword
422 expr = getstring(x, _("size requires an expression")) 421 expr = getstring(x, _("size requires an expression"))
423 m = sizematcher(expr) 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 @predicate('encoding(name)', callexisting=True) 426 @predicate('encoding(name)', callexisting=True)
427 def encoding(mctx, x): 427 def encoding(mctx, x):
428 """File can be successfully decoded with the given character 428 """File can be successfully decoded with the given character
429 encoding. May not be useful for encodings other than ASCII and 429 encoding. May not be useful for encodings other than ASCII and
431 """ 431 """
432 432
433 # i18n: "encoding" is a keyword 433 # i18n: "encoding" is a keyword
434 enc = getstring(x, _("encoding requires an encoding name")) 434 enc = getstring(x, _("encoding requires an encoding name"))
435 435
436 s = [] 436 def encp(fctx):
437 for f in mctx.existing(): 437 d = fctx.data()
438 d = mctx.ctx[f].data()
439 try: 438 try:
440 d.decode(pycompat.sysstr(enc)) 439 d.decode(pycompat.sysstr(enc))
440 return True
441 except LookupError: 441 except LookupError:
442 raise error.Abort(_("unknown encoding '%s'") % enc) 442 raise error.Abort(_("unknown encoding '%s'") % enc)
443 except UnicodeDecodeError: 443 except UnicodeDecodeError:
444 continue 444 return False
445 s.append(f) 445
446 446 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
447 return s
448 447
449 @predicate('eol(style)', callexisting=True) 448 @predicate('eol(style)', callexisting=True)
450 def eol(mctx, x): 449 def eol(mctx, x):
451 """File contains newlines of the given style (dos, unix, mac). Binary 450 """File contains newlines of the given style (dos, unix, mac). Binary
452 files are excluded, files with mixed line endings match multiple 451 files are excluded, files with mixed line endings match multiple
454 """ 453 """
455 454
456 # i18n: "eol" is a keyword 455 # i18n: "eol" is a keyword
457 enc = getstring(x, _("eol requires a style name")) 456 enc = getstring(x, _("eol requires a style name"))
458 457
459 s = [] 458 def eolp(fctx):
460 for f in mctx.existing():
461 fctx = mctx.ctx[f]
462 if fctx.isbinary(): 459 if fctx.isbinary():
463 continue 460 return False
464 d = fctx.data() 461 d = fctx.data()
465 if (enc == 'dos' or enc == 'win') and '\r\n' in d: 462 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
466 s.append(f) 463 return True
467 elif enc == 'unix' and re.search('(?<!\r)\n', d): 464 elif enc == 'unix' and re.search('(?<!\r)\n', d):
468 s.append(f) 465 return True
469 elif enc == 'mac' and re.search('\r(?!\n)', d): 466 elif enc == 'mac' and re.search('\r(?!\n)', d):
470 s.append(f) 467 return True
471 return s 468 return False
469 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
472 470
473 @predicate('copied()') 471 @predicate('copied()')
474 def copied(mctx, x): 472 def copied(mctx, x):
475 """File that is recorded as being copied. 473 """File that is recorded as being copied.
476 """ 474 """
477 # i18n: "copied" is a keyword 475 # i18n: "copied" is a keyword
478 getargs(x, 0, 0, _("copied takes no arguments")) 476 getargs(x, 0, 0, _("copied takes no arguments"))
479 s = [] 477 def copiedp(fctx):
480 for f in mctx.subset: 478 p = fctx.parents()
481 if f in mctx.ctx: 479 return p and p[0].path() != fctx.path()
482 p = mctx.ctx[f].parents() 480 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
483 if p and p[0].path() != f:
484 s.append(f)
485 return s
486 481
487 @predicate('revs(revs, pattern)') 482 @predicate('revs(revs, pattern)')
488 def revs(mctx, x): 483 def revs(mctx, x):
489 """Evaluate set in the specified revisions. If the revset match multiple 484 """Evaluate set in the specified revisions. If the revset match multiple
490 revs, this will return file matching pattern in any of the revision. 485 revs, this will return file matching pattern in any of the revision.
494 # i18n: "revs" is a keyword 489 # i18n: "revs" is a keyword
495 revspec = getstring(r, _("first argument to revs must be a revision")) 490 revspec = getstring(r, _("first argument to revs must be a revision"))
496 repo = mctx.ctx.repo() 491 repo = mctx.ctx.repo()
497 revs = scmutil.revrange(repo, [revspec]) 492 revs = scmutil.revrange(repo, [revspec])
498 493
499 found = set() 494 matchers = []
500 result = []
501 for r in revs: 495 for r in revs:
502 ctx = repo[r] 496 ctx = repo[r]
503 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x): 497 matchers.append(getmatch(mctx.switch(ctx, _buildstatus(ctx, x)), x))
504 if f not in found: 498 if not matchers:
505 found.add(f) 499 return mctx.never()
506 result.append(f) 500 if len(matchers) == 1:
507 return result 501 return matchers[0]
502 return matchmod.unionmatcher(matchers)
508 503
509 @predicate('status(base, rev, pattern)') 504 @predicate('status(base, rev, pattern)')
510 def status(mctx, x): 505 def status(mctx, x):
511 """Evaluate predicate using status change between ``base`` and 506 """Evaluate predicate using status change between ``base`` and
512 ``rev``. Examples: 507 ``rev``. Examples:
524 reverr = _("second argument to status must be a revision") 519 reverr = _("second argument to status must be a revision")
525 revspec = getstring(r, reverr) 520 revspec = getstring(r, reverr)
526 if not revspec: 521 if not revspec:
527 raise error.ParseError(reverr) 522 raise error.ParseError(reverr)
528 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec]) 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 @predicate('subrepo([pattern])') 526 @predicate('subrepo([pattern])')
532 def subrepo(mctx, x): 527 def subrepo(mctx, x):
533 """Subrepositories whose paths match the given pattern. 528 """Subrepositories whose paths match the given pattern.
534 """ 529 """
535 # i18n: "subrepo" is a keyword 530 # i18n: "subrepo" is a keyword
536 getargs(x, 0, 1, _("subrepo takes at most one argument")) 531 getargs(x, 0, 1, _("subrepo takes at most one argument"))
537 ctx = mctx.ctx 532 ctx = mctx.ctx
538 sstate = sorted(ctx.substate) 533 sstate = ctx.substate
539 if x: 534 if x:
540 pat = getpattern(x, matchmod.allpatternkinds, 535 pat = getpattern(x, matchmod.allpatternkinds,
541 # i18n: "subrepo" is a keyword 536 # i18n: "subrepo" is a keyword
542 _("subrepo requires a pattern or no arguments")) 537 _("subrepo requires a pattern or no arguments"))
543 fast = not matchmod.patkind(pat) 538 fast = not matchmod.patkind(pat)
544 if fast: 539 if fast:
545 def m(s): 540 def m(s):
546 return (s == pat) 541 return (s == pat)
547 else: 542 else:
548 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx) 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 else: 546 else:
551 return [sub for sub in sstate] 547 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
552 548
553 methods = { 549 methods = {
554 'string': stringset, 550 'string': stringmatch,
555 'symbol': stringset, 551 'symbol': stringmatch,
556 'kindpat': kindpatset, 552 'kindpat': kindpatmatch,
557 'and': andset, 553 'and': andmatch,
558 'or': orset, 554 'or': ormatch,
559 'minus': minusset, 555 'minus': minusmatch,
560 'negate': negateset, 556 'negate': negatematch,
561 'list': listset, 557 'list': listmatch,
562 'group': getset, 558 'group': getmatch,
563 'not': notset, 559 'not': notmatch,
564 'func': func, 560 'func': func,
565 } 561 }
566 562
567 class matchctx(object): 563 class matchctx(object):
568 def __init__(self, ctx, subset, status=None, badfn=None): 564 def __init__(self, ctx, subset, status=None, badfn=None):
678 else: 674 else:
679 return list(ctx.walk(ctx.match([]))) 675 return list(ctx.walk(ctx.match([])))
680 676
681 def match(ctx, expr, badfn=None): 677 def match(ctx, expr, badfn=None):
682 """Create a matcher for a single fileset expression""" 678 """Create a matcher for a single fileset expression"""
683 repo = ctx.repo()
684 tree = parse(expr) 679 tree = parse(expr)
685 fset = getset(fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn), tree) 680 mctx = fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
686 return matchmod.predicatematcher(repo.root, repo.getcwd(), 681 return getmatch(mctx, tree)
687 fset.__contains__,
688 predrepr='fileset', badfn=badfn)
689 682
690 def _buildstatus(ctx, tree, basectx=None): 683 def _buildstatus(ctx, tree, basectx=None):
691 # do we need status info? 684 # do we need status info?
692 685
693 # temporaty boolean to simplify the next conditional 686 # temporaty boolean to simplify the next conditional