##// END OF EJS Templates
fileset: add helpers to make predicatematcher and nevermatcher...
Yuya Nishihara -
r38706:07b551a4 default
parent child Browse files
Show More
@@ -1,671 +1,720
1 1 # fileset.py - file set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 import errno
10 11 import re
11 12
12 13 from .i18n import _
13 14 from . import (
14 15 error,
15 16 match as matchmod,
16 17 merge,
17 18 parser,
18 19 pycompat,
19 20 registrar,
20 21 scmutil,
21 22 util,
22 23 )
23 24 from .utils import (
24 25 stringutil,
25 26 )
26 27
27 28 elements = {
28 29 # token-type: binding-strength, primary, prefix, infix, suffix
29 30 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
30 31 ":": (15, None, None, ("kindpat", 15), None),
31 32 "-": (5, None, ("negate", 19), ("minus", 5), None),
32 33 "not": (10, None, ("not", 10), None, None),
33 34 "!": (10, None, ("not", 10), None, None),
34 35 "and": (5, None, None, ("and", 5), None),
35 36 "&": (5, None, None, ("and", 5), None),
36 37 "or": (4, None, None, ("or", 4), None),
37 38 "|": (4, None, None, ("or", 4), None),
38 39 "+": (4, None, None, ("or", 4), None),
39 40 ",": (2, None, None, ("list", 2), None),
40 41 ")": (0, None, None, None, None),
41 42 "symbol": (0, "symbol", None, None, None),
42 43 "string": (0, "string", None, None, None),
43 44 "end": (0, None, None, None, None),
44 45 }
45 46
46 47 keywords = {'and', 'or', 'not'}
47 48
48 49 globchars = ".*{}[]?/\\_"
49 50
50 51 def tokenize(program):
51 52 pos, l = 0, len(program)
52 53 program = pycompat.bytestr(program)
53 54 while pos < l:
54 55 c = program[pos]
55 56 if c.isspace(): # skip inter-token whitespace
56 57 pass
57 58 elif c in "(),-:|&+!": # handle simple operators
58 59 yield (c, None, pos)
59 60 elif (c in '"\'' or c == 'r' and
60 61 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
61 62 if c == 'r':
62 63 pos += 1
63 64 c = program[pos]
64 65 decode = lambda x: x
65 66 else:
66 67 decode = parser.unescapestr
67 68 pos += 1
68 69 s = pos
69 70 while pos < l: # find closing quote
70 71 d = program[pos]
71 72 if d == '\\': # skip over escaped characters
72 73 pos += 2
73 74 continue
74 75 if d == c:
75 76 yield ('string', decode(program[s:pos]), s)
76 77 break
77 78 pos += 1
78 79 else:
79 80 raise error.ParseError(_("unterminated string"), s)
80 81 elif c.isalnum() or c in globchars or ord(c) > 127:
81 82 # gather up a symbol/keyword
82 83 s = pos
83 84 pos += 1
84 85 while pos < l: # find end of symbol
85 86 d = program[pos]
86 87 if not (d.isalnum() or d in globchars or ord(d) > 127):
87 88 break
88 89 pos += 1
89 90 sym = program[s:pos]
90 91 if sym in keywords: # operator keywords
91 92 yield (sym, None, s)
92 93 else:
93 94 yield ('symbol', sym, s)
94 95 pos -= 1
95 96 else:
96 97 raise error.ParseError(_("syntax error"), pos)
97 98 pos += 1
98 99 yield ('end', None, pos)
99 100
100 101 def parse(expr):
101 102 p = parser.parser(elements)
102 103 tree, pos = p.parse(tokenize(expr))
103 104 if pos != len(expr):
104 105 raise error.ParseError(_("invalid token"), pos)
105 106 return tree
106 107
107 108 def getsymbol(x):
108 109 if x and x[0] == 'symbol':
109 110 return x[1]
110 111 raise error.ParseError(_('not a symbol'))
111 112
112 113 def getstring(x, err):
113 114 if x and (x[0] == 'string' or x[0] == 'symbol'):
114 115 return x[1]
115 116 raise error.ParseError(err)
116 117
117 118 def _getkindpat(x, y, allkinds, err):
118 119 kind = getsymbol(x)
119 120 pat = getstring(y, err)
120 121 if kind not in allkinds:
121 122 raise error.ParseError(_("invalid pattern kind: %s") % kind)
122 123 return '%s:%s' % (kind, pat)
123 124
124 125 def getpattern(x, allkinds, err):
125 126 if x and x[0] == 'kindpat':
126 127 return _getkindpat(x[1], x[2], allkinds, err)
127 128 return getstring(x, err)
128 129
129 130 def getlist(x):
130 131 if not x:
131 132 return []
132 133 if x[0] == 'list':
133 134 return getlist(x[1]) + [x[2]]
134 135 return [x]
135 136
136 137 def getargs(x, min, max, err):
137 138 l = getlist(x)
138 139 if len(l) < min or len(l) > max:
139 140 raise error.ParseError(err)
140 141 return l
141 142
142 143 def getset(mctx, x):
143 144 if not x:
144 145 raise error.ParseError(_("missing argument"))
145 146 return methods[x[0]](mctx, *x[1:])
146 147
147 148 def stringset(mctx, x):
148 149 m = mctx.matcher([x])
149 150 return [f for f in mctx.subset if m(f)]
150 151
151 152 def kindpatset(mctx, x, y):
152 153 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
153 154 _("pattern must be a string")))
154 155
155 156 def andset(mctx, x, y):
156 157 return getset(mctx.narrow(getset(mctx, x)), y)
157 158
158 159 def orset(mctx, x, y):
159 160 # needs optimizing
160 161 xl = getset(mctx, x)
161 162 yl = getset(mctx, y)
162 163 return xl + [f for f in yl if f not in xl]
163 164
164 165 def notset(mctx, x):
165 166 s = set(getset(mctx, x))
166 167 return [r for r in mctx.subset if r not in s]
167 168
168 169 def minusset(mctx, x, y):
169 170 xl = getset(mctx, x)
170 171 yl = set(getset(mctx, y))
171 172 return [f for f in xl if f not in yl]
172 173
173 174 def negateset(mctx, x):
174 175 raise error.ParseError(_("can't use negate operator in this context"))
175 176
176 177 def listset(mctx, a, b):
177 178 raise error.ParseError(_("can't use a list in this context"),
178 179 hint=_('see hg help "filesets.x or y"'))
179 180
180 181 def func(mctx, a, b):
181 182 funcname = getsymbol(a)
182 183 if funcname in symbols:
183 184 enabled = mctx._existingenabled
184 185 mctx._existingenabled = funcname in _existingcallers
185 186 try:
186 187 return symbols[funcname](mctx, b)
187 188 finally:
188 189 mctx._existingenabled = enabled
189 190
190 191 keep = lambda fn: getattr(fn, '__doc__', None) is not None
191 192
192 193 syms = [s for (s, fn) in symbols.items() if keep(fn)]
193 194 raise error.UnknownIdentifier(funcname, syms)
194 195
195 196 # symbols are callable like:
196 197 # fun(mctx, x)
197 198 # with:
198 199 # mctx - current matchctx instance
199 200 # x - argument in tree form
200 201 symbols = {}
201 202
202 203 # filesets using matchctx.status()
203 204 _statuscallers = set()
204 205
205 206 # filesets using matchctx.existing()
206 207 _existingcallers = set()
207 208
208 209 predicate = registrar.filesetpredicate()
209 210
210 211 @predicate('modified()', callstatus=True)
211 212 def modified(mctx, x):
212 213 """File that is modified according to :hg:`status`.
213 214 """
214 215 # i18n: "modified" is a keyword
215 216 getargs(x, 0, 0, _("modified takes no arguments"))
216 217 s = set(mctx.status().modified)
217 218 return [f for f in mctx.subset if f in s]
218 219
219 220 @predicate('added()', callstatus=True)
220 221 def added(mctx, x):
221 222 """File that is added according to :hg:`status`.
222 223 """
223 224 # i18n: "added" is a keyword
224 225 getargs(x, 0, 0, _("added takes no arguments"))
225 226 s = set(mctx.status().added)
226 227 return [f for f in mctx.subset if f in s]
227 228
228 229 @predicate('removed()', callstatus=True)
229 230 def removed(mctx, x):
230 231 """File that is removed according to :hg:`status`.
231 232 """
232 233 # i18n: "removed" is a keyword
233 234 getargs(x, 0, 0, _("removed takes no arguments"))
234 235 s = set(mctx.status().removed)
235 236 return [f for f in mctx.subset if f in s]
236 237
237 238 @predicate('deleted()', callstatus=True)
238 239 def deleted(mctx, x):
239 240 """Alias for ``missing()``.
240 241 """
241 242 # i18n: "deleted" is a keyword
242 243 getargs(x, 0, 0, _("deleted takes no arguments"))
243 244 s = set(mctx.status().deleted)
244 245 return [f for f in mctx.subset if f in s]
245 246
246 247 @predicate('missing()', callstatus=True)
247 248 def missing(mctx, x):
248 249 """File that is missing according to :hg:`status`.
249 250 """
250 251 # i18n: "missing" is a keyword
251 252 getargs(x, 0, 0, _("missing takes no arguments"))
252 253 s = set(mctx.status().deleted)
253 254 return [f for f in mctx.subset if f in s]
254 255
255 256 @predicate('unknown()', callstatus=True)
256 257 def unknown(mctx, x):
257 258 """File that is unknown according to :hg:`status`. These files will only be
258 259 considered if this predicate is used.
259 260 """
260 261 # i18n: "unknown" is a keyword
261 262 getargs(x, 0, 0, _("unknown takes no arguments"))
262 263 s = set(mctx.status().unknown)
263 264 return [f for f in mctx.subset if f in s]
264 265
265 266 @predicate('ignored()', callstatus=True)
266 267 def ignored(mctx, x):
267 268 """File that is ignored according to :hg:`status`. These files will only be
268 269 considered if this predicate is used.
269 270 """
270 271 # i18n: "ignored" is a keyword
271 272 getargs(x, 0, 0, _("ignored takes no arguments"))
272 273 s = set(mctx.status().ignored)
273 274 return [f for f in mctx.subset if f in s]
274 275
275 276 @predicate('clean()', callstatus=True)
276 277 def clean(mctx, x):
277 278 """File that is clean according to :hg:`status`.
278 279 """
279 280 # i18n: "clean" is a keyword
280 281 getargs(x, 0, 0, _("clean takes no arguments"))
281 282 s = set(mctx.status().clean)
282 283 return [f for f in mctx.subset if f in s]
283 284
284 285 @predicate('binary()', callexisting=True)
285 286 def binary(mctx, x):
286 287 """File that appears to be binary (contains NUL bytes).
287 288 """
288 289 # i18n: "binary" is a keyword
289 290 getargs(x, 0, 0, _("binary takes no arguments"))
290 291 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
291 292
292 293 @predicate('exec()', callexisting=True)
293 294 def exec_(mctx, x):
294 295 """File that is marked as executable.
295 296 """
296 297 # i18n: "exec" is a keyword
297 298 getargs(x, 0, 0, _("exec takes no arguments"))
298 299 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
299 300
300 301 @predicate('symlink()', callexisting=True)
301 302 def symlink(mctx, x):
302 303 """File that is marked as a symlink.
303 304 """
304 305 # i18n: "symlink" is a keyword
305 306 getargs(x, 0, 0, _("symlink takes no arguments"))
306 307 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
307 308
308 309 @predicate('resolved()')
309 310 def resolved(mctx, x):
310 311 """File that is marked resolved according to :hg:`resolve -l`.
311 312 """
312 313 # i18n: "resolved" is a keyword
313 314 getargs(x, 0, 0, _("resolved takes no arguments"))
314 315 if mctx.ctx.rev() is not None:
315 316 return []
316 317 ms = merge.mergestate.read(mctx.ctx.repo())
317 318 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
318 319
319 320 @predicate('unresolved()')
320 321 def unresolved(mctx, x):
321 322 """File that is marked unresolved according to :hg:`resolve -l`.
322 323 """
323 324 # i18n: "unresolved" is a keyword
324 325 getargs(x, 0, 0, _("unresolved takes no arguments"))
325 326 if mctx.ctx.rev() is not None:
326 327 return []
327 328 ms = merge.mergestate.read(mctx.ctx.repo())
328 329 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
329 330
330 331 @predicate('hgignore()')
331 332 def hgignore(mctx, x):
332 333 """File that matches the active .hgignore pattern.
333 334 """
334 335 # i18n: "hgignore" is a keyword
335 336 getargs(x, 0, 0, _("hgignore takes no arguments"))
336 337 ignore = mctx.ctx.repo().dirstate._ignore
337 338 return [f for f in mctx.subset if ignore(f)]
338 339
339 340 @predicate('portable()')
340 341 def portable(mctx, x):
341 342 """File that has a portable name. (This doesn't include filenames with case
342 343 collisions.)
343 344 """
344 345 # i18n: "portable" is a keyword
345 346 getargs(x, 0, 0, _("portable takes no arguments"))
346 347 checkwinfilename = util.checkwinfilename
347 348 return [f for f in mctx.subset if checkwinfilename(f) is None]
348 349
349 350 @predicate('grep(regex)', callexisting=True)
350 351 def grep(mctx, x):
351 352 """File contains the given regular expression.
352 353 """
353 354 try:
354 355 # i18n: "grep" is a keyword
355 356 r = re.compile(getstring(x, _("grep requires a pattern")))
356 357 except re.error as e:
357 358 raise error.ParseError(_('invalid match pattern: %s') %
358 359 stringutil.forcebytestr(e))
359 360 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
360 361
361 362 def _sizetomax(s):
362 363 try:
363 364 s = s.strip().lower()
364 365 for k, v in util._sizeunits:
365 366 if s.endswith(k):
366 367 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
367 368 n = s[:-len(k)]
368 369 inc = 1.0
369 370 if "." in n:
370 371 inc /= 10 ** len(n.split(".")[1])
371 372 return int((float(n) + inc) * v) - 1
372 373 # no extension, this is a precise value
373 374 return int(s)
374 375 except ValueError:
375 376 raise error.ParseError(_("couldn't parse size: %s") % s)
376 377
377 378 def sizematcher(x):
378 379 """Return a function(size) -> bool from the ``size()`` expression"""
379 380
380 381 # i18n: "size" is a keyword
381 382 expr = getstring(x, _("size requires an expression")).strip()
382 383 if '-' in expr: # do we have a range?
383 384 a, b = expr.split('-', 1)
384 385 a = util.sizetoint(a)
385 386 b = util.sizetoint(b)
386 387 return lambda x: x >= a and x <= b
387 388 elif expr.startswith("<="):
388 389 a = util.sizetoint(expr[2:])
389 390 return lambda x: x <= a
390 391 elif expr.startswith("<"):
391 392 a = util.sizetoint(expr[1:])
392 393 return lambda x: x < a
393 394 elif expr.startswith(">="):
394 395 a = util.sizetoint(expr[2:])
395 396 return lambda x: x >= a
396 397 elif expr.startswith(">"):
397 398 a = util.sizetoint(expr[1:])
398 399 return lambda x: x > a
399 400 else:
400 401 a = util.sizetoint(expr)
401 402 b = _sizetomax(expr)
402 403 return lambda x: x >= a and x <= b
403 404
404 405 @predicate('size(expression)', callexisting=True)
405 406 def size(mctx, x):
406 407 """File size matches the given expression. Examples:
407 408
408 409 - size('1k') - files from 1024 to 2047 bytes
409 410 - size('< 20k') - files less than 20480 bytes
410 411 - size('>= .5MB') - files at least 524288 bytes
411 412 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
412 413 """
413 414 m = sizematcher(x)
414 415 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
415 416
416 417 @predicate('encoding(name)', callexisting=True)
417 418 def encoding(mctx, x):
418 419 """File can be successfully decoded with the given character
419 420 encoding. May not be useful for encodings other than ASCII and
420 421 UTF-8.
421 422 """
422 423
423 424 # i18n: "encoding" is a keyword
424 425 enc = getstring(x, _("encoding requires an encoding name"))
425 426
426 427 s = []
427 428 for f in mctx.existing():
428 429 d = mctx.ctx[f].data()
429 430 try:
430 431 d.decode(pycompat.sysstr(enc))
431 432 except LookupError:
432 433 raise error.Abort(_("unknown encoding '%s'") % enc)
433 434 except UnicodeDecodeError:
434 435 continue
435 436 s.append(f)
436 437
437 438 return s
438 439
439 440 @predicate('eol(style)', callexisting=True)
440 441 def eol(mctx, x):
441 442 """File contains newlines of the given style (dos, unix, mac). Binary
442 443 files are excluded, files with mixed line endings match multiple
443 444 styles.
444 445 """
445 446
446 447 # i18n: "eol" is a keyword
447 448 enc = getstring(x, _("eol requires a style name"))
448 449
449 450 s = []
450 451 for f in mctx.existing():
451 452 fctx = mctx.ctx[f]
452 453 if fctx.isbinary():
453 454 continue
454 455 d = fctx.data()
455 456 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
456 457 s.append(f)
457 458 elif enc == 'unix' and re.search('(?<!\r)\n', d):
458 459 s.append(f)
459 460 elif enc == 'mac' and re.search('\r(?!\n)', d):
460 461 s.append(f)
461 462 return s
462 463
463 464 @predicate('copied()')
464 465 def copied(mctx, x):
465 466 """File that is recorded as being copied.
466 467 """
467 468 # i18n: "copied" is a keyword
468 469 getargs(x, 0, 0, _("copied takes no arguments"))
469 470 s = []
470 471 for f in mctx.subset:
471 472 if f in mctx.ctx:
472 473 p = mctx.ctx[f].parents()
473 474 if p and p[0].path() != f:
474 475 s.append(f)
475 476 return s
476 477
477 478 @predicate('revs(revs, pattern)')
478 479 def revs(mctx, x):
479 480 """Evaluate set in the specified revisions. If the revset match multiple
480 481 revs, this will return file matching pattern in any of the revision.
481 482 """
482 483 # i18n: "revs" is a keyword
483 484 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
484 485 # i18n: "revs" is a keyword
485 486 revspec = getstring(r, _("first argument to revs must be a revision"))
486 487 repo = mctx.ctx.repo()
487 488 revs = scmutil.revrange(repo, [revspec])
488 489
489 490 found = set()
490 491 result = []
491 492 for r in revs:
492 493 ctx = repo[r]
493 494 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
494 495 if f not in found:
495 496 found.add(f)
496 497 result.append(f)
497 498 return result
498 499
499 500 @predicate('status(base, rev, pattern)')
500 501 def status(mctx, x):
501 502 """Evaluate predicate using status change between ``base`` and
502 503 ``rev``. Examples:
503 504
504 505 - ``status(3, 7, added())`` - matches files added from "3" to "7"
505 506 """
506 507 repo = mctx.ctx.repo()
507 508 # i18n: "status" is a keyword
508 509 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
509 510 # i18n: "status" is a keyword
510 511 baseerr = _("first argument to status must be a revision")
511 512 baserevspec = getstring(b, baseerr)
512 513 if not baserevspec:
513 514 raise error.ParseError(baseerr)
514 515 reverr = _("second argument to status must be a revision")
515 516 revspec = getstring(r, reverr)
516 517 if not revspec:
517 518 raise error.ParseError(reverr)
518 519 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
519 520 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
520 521
521 522 @predicate('subrepo([pattern])')
522 523 def subrepo(mctx, x):
523 524 """Subrepositories whose paths match the given pattern.
524 525 """
525 526 # i18n: "subrepo" is a keyword
526 527 getargs(x, 0, 1, _("subrepo takes at most one argument"))
527 528 ctx = mctx.ctx
528 529 sstate = sorted(ctx.substate)
529 530 if x:
530 531 pat = getpattern(x, matchmod.allpatternkinds,
531 532 # i18n: "subrepo" is a keyword
532 533 _("subrepo requires a pattern or no arguments"))
533 534 fast = not matchmod.patkind(pat)
534 535 if fast:
535 536 def m(s):
536 537 return (s == pat)
537 538 else:
538 539 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
539 540 return [sub for sub in sstate if m(sub)]
540 541 else:
541 542 return [sub for sub in sstate]
542 543
543 544 methods = {
544 545 'string': stringset,
545 546 'symbol': stringset,
546 547 'kindpat': kindpatset,
547 548 'and': andset,
548 549 'or': orset,
549 550 'minus': minusset,
550 551 'negate': negateset,
551 552 'list': listset,
552 553 'group': getset,
553 554 'not': notset,
554 555 'func': func,
555 556 }
556 557
557 558 class matchctx(object):
558 559 def __init__(self, ctx, subset, status=None, badfn=None):
559 560 self.ctx = ctx
560 561 self.subset = subset
561 562 self._status = status
562 563 self._badfn = badfn
563 564 self._existingenabled = False
564 565 def status(self):
565 566 return self._status
567
566 568 def matcher(self, patterns):
567 569 return self.ctx.match(patterns, badfn=self._badfn)
570
571 def predicate(self, predfn, predrepr=None, cache=False):
572 """Create a matcher to select files by predfn(filename)"""
573 if cache:
574 predfn = util.cachefunc(predfn)
575 repo = self.ctx.repo()
576 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
577 predrepr=predrepr, badfn=self._badfn)
578
579 def fpredicate(self, predfn, predrepr=None, cache=False):
580 """Create a matcher to select files by predfn(fctx) at the current
581 revision
582
583 Missing files are ignored.
584 """
585 ctx = self.ctx
586 if ctx.rev() is None:
587 def fctxpredfn(f):
588 try:
589 fctx = ctx[f]
590 except error.LookupError:
591 return False
592 try:
593 fctx.audit()
594 except error.Abort:
595 return False
596 try:
597 return predfn(fctx)
598 except (IOError, OSError) as e:
599 if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EISDIR):
600 return False
601 raise
602 else:
603 def fctxpredfn(f):
604 try:
605 fctx = ctx[f]
606 except error.LookupError:
607 return False
608 return predfn(fctx)
609 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
610
611 def never(self):
612 """Create a matcher to select nothing"""
613 repo = self.ctx.repo()
614 return matchmod.nevermatcher(repo.root, repo.getcwd(),
615 badfn=self._badfn)
616
568 617 def filter(self, files):
569 618 return [f for f in files if f in self.subset]
570 619 def existing(self):
571 620 if not self._existingenabled:
572 621 raise error.ProgrammingError('unexpected existing() invocation')
573 622 if self._status is not None:
574 623 removed = set(self._status[3])
575 624 unknown = set(self._status[4] + self._status[5])
576 625 else:
577 626 removed = set()
578 627 unknown = set()
579 628 return (f for f in self.subset
580 629 if (f in self.ctx and f not in removed) or f in unknown)
581 630 def narrow(self, files):
582 631 return matchctx(self.ctx, self.filter(files), self._status, self._badfn)
583 632 def switch(self, ctx, status=None):
584 633 subset = self.filter(_buildsubset(ctx, status))
585 634 return matchctx(ctx, subset, status, self._badfn)
586 635
587 636 class fullmatchctx(matchctx):
588 637 """A match context where any files in any revisions should be valid"""
589 638
590 639 def __init__(self, ctx, status=None, badfn=None):
591 640 subset = _buildsubset(ctx, status)
592 641 super(fullmatchctx, self).__init__(ctx, subset, status, badfn)
593 642 def switch(self, ctx, status=None):
594 643 return fullmatchctx(ctx, status, self._badfn)
595 644
596 645 # filesets using matchctx.switch()
597 646 _switchcallers = [
598 647 'revs',
599 648 'status',
600 649 ]
601 650
602 651 def _intree(funcs, tree):
603 652 if isinstance(tree, tuple):
604 653 if tree[0] == 'func' and tree[1][0] == 'symbol':
605 654 if tree[1][1] in funcs:
606 655 return True
607 656 if tree[1][1] in _switchcallers:
608 657 # arguments won't be evaluated in the current context
609 658 return False
610 659 for s in tree[1:]:
611 660 if _intree(funcs, s):
612 661 return True
613 662 return False
614 663
615 664 def _buildsubset(ctx, status):
616 665 if status:
617 666 subset = []
618 667 for c in status:
619 668 subset.extend(c)
620 669 return subset
621 670 else:
622 671 return list(ctx.walk(ctx.match([])))
623 672
624 673 def match(ctx, expr, badfn=None):
625 674 """Create a matcher for a single fileset expression"""
626 675 repo = ctx.repo()
627 676 tree = parse(expr)
628 677 fset = getset(fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn), tree)
629 678 return matchmod.predicatematcher(repo.root, repo.getcwd(),
630 679 fset.__contains__,
631 680 predrepr='fileset', badfn=badfn)
632 681
633 682 def _buildstatus(ctx, tree, basectx=None):
634 683 # do we need status info?
635 684
636 685 # temporaty boolean to simplify the next conditional
637 686 purewdir = ctx.rev() is None and basectx is None
638 687
639 688 if (_intree(_statuscallers, tree) or
640 689 # Using matchctx.existing() on a workingctx requires us to check
641 690 # for deleted files.
642 691 (purewdir and _intree(_existingcallers, tree))):
643 692 unknown = _intree(['unknown'], tree)
644 693 ignored = _intree(['ignored'], tree)
645 694
646 695 r = ctx.repo()
647 696 if basectx is None:
648 697 basectx = ctx.p1()
649 698 return r.status(basectx, ctx,
650 699 unknown=unknown, ignored=ignored, clean=True)
651 700 else:
652 701 return None
653 702
654 703 def prettyformat(tree):
655 704 return parser.prettyformat(tree, ('string', 'symbol'))
656 705
657 706 def loadpredicate(ui, extname, registrarobj):
658 707 """Load fileset predicates from specified registrarobj
659 708 """
660 709 for name, func in registrarobj._table.iteritems():
661 710 symbols[name] = func
662 711 if func._callstatus:
663 712 _statuscallers.add(name)
664 713 if func._callexisting:
665 714 _existingcallers.add(name)
666 715
667 716 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
668 717 loadpredicate(None, None, predicate)
669 718
670 719 # tell hggettext to extract docstrings from these functions:
671 720 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now