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