##// END OF EJS Templates
help: filesets show hg status command
timeless@mozdev.org -
r26194:fc65d63e default
parent child Browse files
Show More
@@ -1,537 +1,537 b''
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 re
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 error,
15 15 merge,
16 16 parser,
17 17 util,
18 18 )
19 19
20 20 elements = {
21 21 # token-type: binding-strength, primary, prefix, infix, suffix
22 22 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
23 23 "-": (5, None, ("negate", 19), ("minus", 5), None),
24 24 "not": (10, None, ("not", 10), None, None),
25 25 "!": (10, None, ("not", 10), None, None),
26 26 "and": (5, None, None, ("and", 5), None),
27 27 "&": (5, None, None, ("and", 5), None),
28 28 "or": (4, None, None, ("or", 4), None),
29 29 "|": (4, None, None, ("or", 4), None),
30 30 "+": (4, None, None, ("or", 4), None),
31 31 ",": (2, None, None, ("list", 2), None),
32 32 ")": (0, None, None, None, None),
33 33 "symbol": (0, "symbol", None, None, None),
34 34 "string": (0, "string", None, None, None),
35 35 "end": (0, None, None, None, None),
36 36 }
37 37
38 38 keywords = set(['and', 'or', 'not'])
39 39
40 40 globchars = ".*{}[]?/\\_"
41 41
42 42 def tokenize(program):
43 43 pos, l = 0, len(program)
44 44 while pos < l:
45 45 c = program[pos]
46 46 if c.isspace(): # skip inter-token whitespace
47 47 pass
48 48 elif c in "(),-|&+!": # handle simple operators
49 49 yield (c, None, pos)
50 50 elif (c in '"\'' or c == 'r' and
51 51 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
52 52 if c == 'r':
53 53 pos += 1
54 54 c = program[pos]
55 55 decode = lambda x: x
56 56 else:
57 57 decode = lambda x: x.decode('string-escape')
58 58 pos += 1
59 59 s = pos
60 60 while pos < l: # find closing quote
61 61 d = program[pos]
62 62 if d == '\\': # skip over escaped characters
63 63 pos += 2
64 64 continue
65 65 if d == c:
66 66 yield ('string', decode(program[s:pos]), s)
67 67 break
68 68 pos += 1
69 69 else:
70 70 raise error.ParseError(_("unterminated string"), s)
71 71 elif c.isalnum() or c in globchars or ord(c) > 127:
72 72 # gather up a symbol/keyword
73 73 s = pos
74 74 pos += 1
75 75 while pos < l: # find end of symbol
76 76 d = program[pos]
77 77 if not (d.isalnum() or d in globchars or ord(d) > 127):
78 78 break
79 79 pos += 1
80 80 sym = program[s:pos]
81 81 if sym in keywords: # operator keywords
82 82 yield (sym, None, s)
83 83 else:
84 84 yield ('symbol', sym, s)
85 85 pos -= 1
86 86 else:
87 87 raise error.ParseError(_("syntax error"), pos)
88 88 pos += 1
89 89 yield ('end', None, pos)
90 90
91 91 def parse(expr):
92 92 p = parser.parser(elements)
93 93 tree, pos = p.parse(tokenize(expr))
94 94 if pos != len(expr):
95 95 raise error.ParseError(_("invalid token"), pos)
96 96 return tree
97 97
98 98 def getstring(x, err):
99 99 if x and (x[0] == 'string' or x[0] == 'symbol'):
100 100 return x[1]
101 101 raise error.ParseError(err)
102 102
103 103 def getset(mctx, x):
104 104 if not x:
105 105 raise error.ParseError(_("missing argument"))
106 106 return methods[x[0]](mctx, *x[1:])
107 107
108 108 def stringset(mctx, x):
109 109 m = mctx.matcher([x])
110 110 return [f for f in mctx.subset if m(f)]
111 111
112 112 def andset(mctx, x, y):
113 113 return getset(mctx.narrow(getset(mctx, x)), y)
114 114
115 115 def orset(mctx, x, y):
116 116 # needs optimizing
117 117 xl = getset(mctx, x)
118 118 yl = getset(mctx, y)
119 119 return xl + [f for f in yl if f not in xl]
120 120
121 121 def notset(mctx, x):
122 122 s = set(getset(mctx, x))
123 123 return [r for r in mctx.subset if r not in s]
124 124
125 125 def minusset(mctx, x, y):
126 126 xl = getset(mctx, x)
127 127 yl = set(getset(mctx, y))
128 128 return [f for f in xl if f not in yl]
129 129
130 130 def listset(mctx, a, b):
131 131 raise error.ParseError(_("can't use a list in this context"))
132 132
133 133 def modified(mctx, x):
134 134 """``modified()``
135 File that is modified according to status.
135 File that is modified according to :hg:`status`.
136 136 """
137 137 # i18n: "modified" is a keyword
138 138 getargs(x, 0, 0, _("modified takes no arguments"))
139 139 s = mctx.status().modified
140 140 return [f for f in mctx.subset if f in s]
141 141
142 142 def added(mctx, x):
143 143 """``added()``
144 File that is added according to status.
144 File that is added according to :hg:`status`.
145 145 """
146 146 # i18n: "added" is a keyword
147 147 getargs(x, 0, 0, _("added takes no arguments"))
148 148 s = mctx.status().added
149 149 return [f for f in mctx.subset if f in s]
150 150
151 151 def removed(mctx, x):
152 152 """``removed()``
153 File that is removed according to status.
153 File that is removed according to :hg:`status`.
154 154 """
155 155 # i18n: "removed" is a keyword
156 156 getargs(x, 0, 0, _("removed takes no arguments"))
157 157 s = mctx.status().removed
158 158 return [f for f in mctx.subset if f in s]
159 159
160 160 def deleted(mctx, x):
161 161 """``deleted()``
162 File that is deleted according to status.
162 File that is deleted according to :hg:`status`.
163 163 """
164 164 # i18n: "deleted" is a keyword
165 165 getargs(x, 0, 0, _("deleted takes no arguments"))
166 166 s = mctx.status().deleted
167 167 return [f for f in mctx.subset if f in s]
168 168
169 169 def unknown(mctx, x):
170 170 """``unknown()``
171 File that is unknown according to status. These files will only be
171 File that is unknown according to :hg:`status`. These files will only be
172 172 considered if this predicate is used.
173 173 """
174 174 # i18n: "unknown" is a keyword
175 175 getargs(x, 0, 0, _("unknown takes no arguments"))
176 176 s = mctx.status().unknown
177 177 return [f for f in mctx.subset if f in s]
178 178
179 179 def ignored(mctx, x):
180 180 """``ignored()``
181 File that is ignored according to status. These files will only be
181 File that is ignored according to :hg:`status`. These files will only be
182 182 considered if this predicate is used.
183 183 """
184 184 # i18n: "ignored" is a keyword
185 185 getargs(x, 0, 0, _("ignored takes no arguments"))
186 186 s = mctx.status().ignored
187 187 return [f for f in mctx.subset if f in s]
188 188
189 189 def clean(mctx, x):
190 190 """``clean()``
191 File that is clean according to status.
191 File that is clean according to :hg:`status`.
192 192 """
193 193 # i18n: "clean" is a keyword
194 194 getargs(x, 0, 0, _("clean takes no arguments"))
195 195 s = mctx.status().clean
196 196 return [f for f in mctx.subset if f in s]
197 197
198 198 def func(mctx, a, b):
199 199 if a[0] == 'symbol' and a[1] in symbols:
200 200 return symbols[a[1]](mctx, b)
201 201
202 202 keep = lambda fn: getattr(fn, '__doc__', None) is not None
203 203
204 204 syms = [s for (s, fn) in symbols.items() if keep(fn)]
205 205 raise error.UnknownIdentifier(a[1], syms)
206 206
207 207 def getlist(x):
208 208 if not x:
209 209 return []
210 210 if x[0] == 'list':
211 211 return getlist(x[1]) + [x[2]]
212 212 return [x]
213 213
214 214 def getargs(x, min, max, err):
215 215 l = getlist(x)
216 216 if len(l) < min or len(l) > max:
217 217 raise error.ParseError(err)
218 218 return l
219 219
220 220 def binary(mctx, x):
221 221 """``binary()``
222 222 File that appears to be binary (contains NUL bytes).
223 223 """
224 224 # i18n: "binary" is a keyword
225 225 getargs(x, 0, 0, _("binary takes no arguments"))
226 226 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
227 227
228 228 def exec_(mctx, x):
229 229 """``exec()``
230 230 File that is marked as executable.
231 231 """
232 232 # i18n: "exec" is a keyword
233 233 getargs(x, 0, 0, _("exec takes no arguments"))
234 234 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
235 235
236 236 def symlink(mctx, x):
237 237 """``symlink()``
238 238 File that is marked as a symlink.
239 239 """
240 240 # i18n: "symlink" is a keyword
241 241 getargs(x, 0, 0, _("symlink takes no arguments"))
242 242 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
243 243
244 244 def resolved(mctx, x):
245 245 """``resolved()``
246 246 File that is marked resolved according to the resolve state.
247 247 """
248 248 # i18n: "resolved" is a keyword
249 249 getargs(x, 0, 0, _("resolved takes no arguments"))
250 250 if mctx.ctx.rev() is not None:
251 251 return []
252 252 ms = merge.mergestate(mctx.ctx.repo())
253 253 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
254 254
255 255 def unresolved(mctx, x):
256 256 """``unresolved()``
257 257 File that is marked unresolved according to the resolve state.
258 258 """
259 259 # i18n: "unresolved" is a keyword
260 260 getargs(x, 0, 0, _("unresolved takes no arguments"))
261 261 if mctx.ctx.rev() is not None:
262 262 return []
263 263 ms = merge.mergestate(mctx.ctx.repo())
264 264 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
265 265
266 266 def hgignore(mctx, x):
267 267 """``hgignore()``
268 268 File that matches the active .hgignore pattern.
269 269 """
270 270 # i18n: "hgignore" is a keyword
271 271 getargs(x, 0, 0, _("hgignore takes no arguments"))
272 272 ignore = mctx.ctx.repo().dirstate._ignore
273 273 return [f for f in mctx.subset if ignore(f)]
274 274
275 275 def portable(mctx, x):
276 276 """``portable()``
277 277 File that has a portable name. (This doesn't include filenames with case
278 278 collisions.)
279 279 """
280 280 # i18n: "portable" is a keyword
281 281 getargs(x, 0, 0, _("portable takes no arguments"))
282 282 checkwinfilename = util.checkwinfilename
283 283 return [f for f in mctx.subset if checkwinfilename(f) is None]
284 284
285 285 def grep(mctx, x):
286 286 """``grep(regex)``
287 287 File contains the given regular expression.
288 288 """
289 289 try:
290 290 # i18n: "grep" is a keyword
291 291 r = re.compile(getstring(x, _("grep requires a pattern")))
292 292 except re.error as e:
293 293 raise error.ParseError(_('invalid match pattern: %s') % e)
294 294 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
295 295
296 296 def _sizetomax(s):
297 297 try:
298 298 s = s.strip().lower()
299 299 for k, v in util._sizeunits:
300 300 if s.endswith(k):
301 301 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
302 302 n = s[:-len(k)]
303 303 inc = 1.0
304 304 if "." in n:
305 305 inc /= 10 ** len(n.split(".")[1])
306 306 return int((float(n) + inc) * v) - 1
307 307 # no extension, this is a precise value
308 308 return int(s)
309 309 except ValueError:
310 310 raise error.ParseError(_("couldn't parse size: %s") % s)
311 311
312 312 def size(mctx, x):
313 313 """``size(expression)``
314 314 File size matches the given expression. Examples:
315 315
316 316 - 1k (files from 1024 to 2047 bytes)
317 317 - < 20k (files less than 20480 bytes)
318 318 - >= .5MB (files at least 524288 bytes)
319 319 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
320 320 """
321 321
322 322 # i18n: "size" is a keyword
323 323 expr = getstring(x, _("size requires an expression")).strip()
324 324 if '-' in expr: # do we have a range?
325 325 a, b = expr.split('-', 1)
326 326 a = util.sizetoint(a)
327 327 b = util.sizetoint(b)
328 328 m = lambda x: x >= a and x <= b
329 329 elif expr.startswith("<="):
330 330 a = util.sizetoint(expr[2:])
331 331 m = lambda x: x <= a
332 332 elif expr.startswith("<"):
333 333 a = util.sizetoint(expr[1:])
334 334 m = lambda x: x < a
335 335 elif expr.startswith(">="):
336 336 a = util.sizetoint(expr[2:])
337 337 m = lambda x: x >= a
338 338 elif expr.startswith(">"):
339 339 a = util.sizetoint(expr[1:])
340 340 m = lambda x: x > a
341 341 elif expr[0].isdigit or expr[0] == '.':
342 342 a = util.sizetoint(expr)
343 343 b = _sizetomax(expr)
344 344 m = lambda x: x >= a and x <= b
345 345 else:
346 346 raise error.ParseError(_("couldn't parse size: %s") % expr)
347 347
348 348 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
349 349
350 350 def encoding(mctx, x):
351 351 """``encoding(name)``
352 352 File can be successfully decoded with the given character
353 353 encoding. May not be useful for encodings other than ASCII and
354 354 UTF-8.
355 355 """
356 356
357 357 # i18n: "encoding" is a keyword
358 358 enc = getstring(x, _("encoding requires an encoding name"))
359 359
360 360 s = []
361 361 for f in mctx.existing():
362 362 d = mctx.ctx[f].data()
363 363 try:
364 364 d.decode(enc)
365 365 except LookupError:
366 366 raise util.Abort(_("unknown encoding '%s'") % enc)
367 367 except UnicodeDecodeError:
368 368 continue
369 369 s.append(f)
370 370
371 371 return s
372 372
373 373 def eol(mctx, x):
374 374 """``eol(style)``
375 375 File contains newlines of the given style (dos, unix, mac). Binary
376 376 files are excluded, files with mixed line endings match multiple
377 377 styles.
378 378 """
379 379
380 380 # i18n: "encoding" is a keyword
381 381 enc = getstring(x, _("encoding requires an encoding name"))
382 382
383 383 s = []
384 384 for f in mctx.existing():
385 385 d = mctx.ctx[f].data()
386 386 if util.binary(d):
387 387 continue
388 388 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
389 389 s.append(f)
390 390 elif enc == 'unix' and re.search('(?<!\r)\n', d):
391 391 s.append(f)
392 392 elif enc == 'mac' and re.search('\r(?!\n)', d):
393 393 s.append(f)
394 394 return s
395 395
396 396 def copied(mctx, x):
397 397 """``copied()``
398 398 File that is recorded as being copied.
399 399 """
400 400 # i18n: "copied" is a keyword
401 401 getargs(x, 0, 0, _("copied takes no arguments"))
402 402 s = []
403 403 for f in mctx.subset:
404 404 p = mctx.ctx[f].parents()
405 405 if p and p[0].path() != f:
406 406 s.append(f)
407 407 return s
408 408
409 409 def subrepo(mctx, x):
410 410 """``subrepo([pattern])``
411 411 Subrepositories whose paths match the given pattern.
412 412 """
413 413 # i18n: "subrepo" is a keyword
414 414 getargs(x, 0, 1, _("subrepo takes at most one argument"))
415 415 ctx = mctx.ctx
416 416 sstate = sorted(ctx.substate)
417 417 if x:
418 418 # i18n: "subrepo" is a keyword
419 419 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
420 420
421 421 from . import match as matchmod # avoid circular import issues
422 422 fast = not matchmod.patkind(pat)
423 423 if fast:
424 424 def m(s):
425 425 return (s == pat)
426 426 else:
427 427 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
428 428 return [sub for sub in sstate if m(sub)]
429 429 else:
430 430 return [sub for sub in sstate]
431 431
432 432 symbols = {
433 433 'added': added,
434 434 'binary': binary,
435 435 'clean': clean,
436 436 'copied': copied,
437 437 'deleted': deleted,
438 438 'encoding': encoding,
439 439 'eol': eol,
440 440 'exec': exec_,
441 441 'grep': grep,
442 442 'ignored': ignored,
443 443 'hgignore': hgignore,
444 444 'modified': modified,
445 445 'portable': portable,
446 446 'removed': removed,
447 447 'resolved': resolved,
448 448 'size': size,
449 449 'symlink': symlink,
450 450 'unknown': unknown,
451 451 'unresolved': unresolved,
452 452 'subrepo': subrepo,
453 453 }
454 454
455 455 methods = {
456 456 'string': stringset,
457 457 'symbol': stringset,
458 458 'and': andset,
459 459 'or': orset,
460 460 'minus': minusset,
461 461 'list': listset,
462 462 'group': getset,
463 463 'not': notset,
464 464 'func': func,
465 465 }
466 466
467 467 class matchctx(object):
468 468 def __init__(self, ctx, subset=None, status=None):
469 469 self.ctx = ctx
470 470 self.subset = subset
471 471 self._status = status
472 472 def status(self):
473 473 return self._status
474 474 def matcher(self, patterns):
475 475 return self.ctx.match(patterns)
476 476 def filter(self, files):
477 477 return [f for f in files if f in self.subset]
478 478 def existing(self):
479 479 if self._status is not None:
480 480 removed = set(self._status[3])
481 481 unknown = set(self._status[4] + self._status[5])
482 482 else:
483 483 removed = set()
484 484 unknown = set()
485 485 return (f for f in self.subset
486 486 if (f in self.ctx and f not in removed) or f in unknown)
487 487 def narrow(self, files):
488 488 return matchctx(self.ctx, self.filter(files), self._status)
489 489
490 490 def _intree(funcs, tree):
491 491 if isinstance(tree, tuple):
492 492 if tree[0] == 'func' and tree[1][0] == 'symbol':
493 493 if tree[1][1] in funcs:
494 494 return True
495 495 for s in tree[1:]:
496 496 if _intree(funcs, s):
497 497 return True
498 498 return False
499 499
500 500 # filesets using matchctx.existing()
501 501 _existingcallers = [
502 502 'binary',
503 503 'exec',
504 504 'grep',
505 505 'size',
506 506 'symlink',
507 507 ]
508 508
509 509 def getfileset(ctx, expr):
510 510 tree = parse(expr)
511 511
512 512 # do we need status info?
513 513 if (_intree(['modified', 'added', 'removed', 'deleted',
514 514 'unknown', 'ignored', 'clean'], tree) or
515 515 # Using matchctx.existing() on a workingctx requires us to check
516 516 # for deleted files.
517 517 (ctx.rev() is None and _intree(_existingcallers, tree))):
518 518 unknown = _intree(['unknown'], tree)
519 519 ignored = _intree(['ignored'], tree)
520 520
521 521 r = ctx.repo()
522 522 status = r.status(ctx.p1(), ctx,
523 523 unknown=unknown, ignored=ignored, clean=True)
524 524 subset = []
525 525 for c in status:
526 526 subset.extend(c)
527 527 else:
528 528 status = None
529 529 subset = list(ctx.walk(ctx.match([])))
530 530
531 531 return getset(matchctx(ctx, subset, status), tree)
532 532
533 533 def prettyformat(tree):
534 534 return parser.prettyformat(tree, ('string', 'symbol'))
535 535
536 536 # tell hggettext to extract docstrings from these functions:
537 537 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now