Mercurial > public > mercurial-scm > hg-stable
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 |