##// END OF EJS Templates
fileset: flatten arguments list...
Yuya Nishihara -
r38839:4dc498d6 default
parent child Browse files
Show More
@@ -1,672 +1,672 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 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 return tree
106 return parser.simplifyinfixops(tree, {'list'})
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 return getlist(x[1]) + [x[2]]
134 return list(x[1:])
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 def listmatch(mctx, x, y):
177 def listmatch(mctx, *xs):
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 595 # open()-ing a directory fails with EACCES on Windows
596 596 if e.errno in (errno.ENOENT, errno.EACCES, errno.ENOTDIR,
597 597 errno.EISDIR):
598 598 return False
599 599 raise
600 600 else:
601 601 def fctxpredfn(f):
602 602 try:
603 603 fctx = ctx[f]
604 604 except error.LookupError:
605 605 return False
606 606 return predfn(fctx)
607 607 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
608 608
609 609 def never(self):
610 610 """Create a matcher to select nothing"""
611 611 repo = self.ctx.repo()
612 612 return matchmod.nevermatcher(repo.root, repo.getcwd(),
613 613 badfn=self._badfn)
614 614
615 615 def switch(self, ctx, status=None):
616 616 return matchctx(ctx, status, self._badfn)
617 617
618 618 # filesets using matchctx.switch()
619 619 _switchcallers = [
620 620 'revs',
621 621 'status',
622 622 ]
623 623
624 624 def _intree(funcs, tree):
625 625 if isinstance(tree, tuple):
626 626 if tree[0] == 'func' and tree[1][0] == 'symbol':
627 627 if tree[1][1] in funcs:
628 628 return True
629 629 if tree[1][1] in _switchcallers:
630 630 # arguments won't be evaluated in the current context
631 631 return False
632 632 for s in tree[1:]:
633 633 if _intree(funcs, s):
634 634 return True
635 635 return False
636 636
637 637 def match(ctx, expr, badfn=None):
638 638 """Create a matcher for a single fileset expression"""
639 639 tree = parse(expr)
640 640 mctx = matchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
641 641 return getmatch(mctx, tree)
642 642
643 643 def _buildstatus(ctx, tree, basectx=None):
644 644 # do we need status info?
645 645
646 646 if _intree(_statuscallers, tree):
647 647 unknown = _intree(['unknown'], tree)
648 648 ignored = _intree(['ignored'], tree)
649 649
650 650 if basectx is None:
651 651 basectx = ctx.p1()
652 652 return basectx.status(ctx, listunknown=unknown, listignored=ignored,
653 653 listclean=True)
654 654 else:
655 655 return None
656 656
657 657 def prettyformat(tree):
658 658 return parser.prettyformat(tree, ('string', 'symbol'))
659 659
660 660 def loadpredicate(ui, extname, registrarobj):
661 661 """Load fileset predicates from specified registrarobj
662 662 """
663 663 for name, func in registrarobj._table.iteritems():
664 664 symbols[name] = func
665 665 if func._callstatus:
666 666 _statuscallers.add(name)
667 667
668 668 # load built-in predicates explicitly to setup _statuscallers
669 669 loadpredicate(None, None, predicate)
670 670
671 671 # tell hggettext to extract docstrings from these functions:
672 672 i18nfunctions = symbols.values()
@@ -1,736 +1,745 b''
1 1 $ fileset() {
2 2 > hg debugfileset --all-files "$@"
3 3 > }
4 4
5 5 $ hg init repo
6 6 $ cd repo
7 7 $ echo a > a1
8 8 $ echo a > a2
9 9 $ echo b > b1
10 10 $ echo b > b2
11 11 $ hg ci -Am addfiles
12 12 adding a1
13 13 adding a2
14 14 adding b1
15 15 adding b2
16 16
17 17 Test operators and basic patterns
18 18
19 19 $ fileset -v a1
20 20 (symbol 'a1')
21 21 * matcher:
22 22 <patternmatcher patterns='(?:a1$)'>
23 23 a1
24 24 $ fileset -v 'a*'
25 25 (symbol 'a*')
26 26 * matcher:
27 27 <patternmatcher patterns='(?:a[^/]*$)'>
28 28 a1
29 29 a2
30 30 $ fileset -v '"re:a\d"'
31 31 (string 're:a\\d')
32 32 * matcher:
33 33 <patternmatcher patterns='(?:a\\d)'>
34 34 a1
35 35 a2
36 36 $ fileset -v '!re:"a\d"'
37 37 (not
38 38 (kindpat
39 39 (symbol 're')
40 40 (string 'a\\d')))
41 41 * matcher:
42 42 <predicatenmatcher
43 43 pred=<not
44 44 <patternmatcher patterns='(?:a\\d)'>>>
45 45 b1
46 46 b2
47 47 $ fileset -v 'path:a1 or glob:b?'
48 48 (or
49 49 (kindpat
50 50 (symbol 'path')
51 51 (symbol 'a1'))
52 52 (kindpat
53 53 (symbol 'glob')
54 54 (symbol 'b?')))
55 55 * matcher:
56 56 <unionmatcher matchers=[
57 57 <patternmatcher patterns='(?:a1(?:/|$))'>,
58 58 <patternmatcher patterns='(?:b.$)'>]>
59 59 a1
60 60 b1
61 61 b2
62 62 $ fileset -v --no-show-matcher 'a1 or a2'
63 63 (or
64 64 (symbol 'a1')
65 65 (symbol 'a2'))
66 66 a1
67 67 a2
68 68 $ fileset 'a1 | a2'
69 69 a1
70 70 a2
71 71 $ fileset 'a* and "*1"'
72 72 a1
73 73 $ fileset 'a* & "*1"'
74 74 a1
75 75 $ fileset 'not (r"a*")'
76 76 b1
77 77 b2
78 78 $ fileset '! ("a*")'
79 79 b1
80 80 b2
81 81 $ fileset 'a* - a1'
82 82 a2
83 83 $ fileset 'a_b'
84 84 $ fileset '"\xy"'
85 85 hg: parse error: invalid \x escape* (glob)
86 86 [255]
87 87
88 88 Test invalid syntax
89 89
90 90 $ fileset -v '"added"()'
91 91 (func
92 92 (string 'added')
93 93 None)
94 94 hg: parse error: not a symbol
95 95 [255]
96 96 $ fileset -v '()()'
97 97 (func
98 98 (group
99 99 None)
100 100 None)
101 101 hg: parse error: not a symbol
102 102 [255]
103 103 $ fileset -v -- '-x'
104 104 (negate
105 105 (symbol 'x'))
106 106 hg: parse error: can't use negate operator in this context
107 107 [255]
108 108 $ fileset -v -- '-()'
109 109 (negate
110 110 (group
111 111 None))
112 112 hg: parse error: can't use negate operator in this context
113 113 [255]
114 $ fileset -p parsed 'a, b, c'
115 * parsed:
116 (list
117 (symbol 'a')
118 (symbol 'b')
119 (symbol 'c'))
120 hg: parse error: can't use a list in this context
121 (see hg help "filesets.x or y")
122 [255]
114 123
115 124 $ fileset '"path":.'
116 125 hg: parse error: not a symbol
117 126 [255]
118 127 $ fileset 'path:foo bar'
119 128 hg: parse error at 9: invalid token
120 129 [255]
121 130 $ fileset 'foo:bar:baz'
122 131 hg: parse error: not a symbol
123 132 [255]
124 133 $ fileset 'foo:bar()'
125 134 hg: parse error: pattern must be a string
126 135 [255]
127 136 $ fileset 'foo:bar'
128 137 hg: parse error: invalid pattern kind: foo
129 138 [255]
130 139
131 140 Show parsed tree at stages:
132 141
133 142 $ fileset -p unknown a
134 143 abort: invalid stage name: unknown
135 144 [255]
136 145
137 146 $ fileset -p parsed 'path:a1 or glob:b?'
138 147 * parsed:
139 148 (or
140 149 (kindpat
141 150 (symbol 'path')
142 151 (symbol 'a1'))
143 152 (kindpat
144 153 (symbol 'glob')
145 154 (symbol 'b?')))
146 155 a1
147 156 b1
148 157 b2
149 158
150 159 $ fileset -p all -s 'a1 or a2 or (grep("b") & clean())'
151 160 * parsed:
152 161 (or
153 162 (or
154 163 (symbol 'a1')
155 164 (symbol 'a2'))
156 165 (group
157 166 (and
158 167 (func
159 168 (symbol 'grep')
160 169 (string 'b'))
161 170 (func
162 171 (symbol 'clean')
163 172 None))))
164 173 * matcher:
165 174 <unionmatcher matchers=[
166 175 <unionmatcher matchers=[
167 176 <patternmatcher patterns='(?:a1$)'>,
168 177 <patternmatcher patterns='(?:a2$)'>]>,
169 178 <intersectionmatcher
170 179 m1=<predicatenmatcher pred=grep('b')>,
171 180 m2=<predicatenmatcher pred=clean>>]>
172 181 a1
173 182 a2
174 183 b1
175 184 b2
176 185
177 186 Test files status
178 187
179 188 $ rm a1
180 189 $ hg rm a2
181 190 $ echo b >> b2
182 191 $ hg cp b1 c1
183 192 $ echo c > c2
184 193 $ echo c > c3
185 194 $ cat > .hgignore <<EOF
186 195 > \.hgignore
187 196 > 2$
188 197 > EOF
189 198 $ fileset 'modified()'
190 199 b2
191 200 $ fileset 'added()'
192 201 c1
193 202 $ fileset 'removed()'
194 203 a2
195 204 $ fileset 'deleted()'
196 205 a1
197 206 $ fileset 'missing()'
198 207 a1
199 208 $ fileset 'unknown()'
200 209 c3
201 210 $ fileset 'ignored()'
202 211 .hgignore
203 212 c2
204 213 $ fileset 'hgignore()'
205 214 .hgignore
206 215 a2
207 216 b2
208 217 c2
209 218 $ fileset 'clean()'
210 219 b1
211 220 $ fileset 'copied()'
212 221 c1
213 222
214 223 Test files status in different revisions
215 224
216 225 $ hg status -m
217 226 M b2
218 227 $ fileset -r0 'revs("wdir()", modified())' --traceback
219 228 b2
220 229 $ hg status -a
221 230 A c1
222 231 $ fileset -r0 'revs("wdir()", added())'
223 232 c1
224 233 $ hg status --change 0 -a
225 234 A a1
226 235 A a2
227 236 A b1
228 237 A b2
229 238 $ hg status -mru
230 239 M b2
231 240 R a2
232 241 ? c3
233 242 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
234 243 a2
235 244 b2
236 245 $ fileset -r0 'added() or revs("wdir()", added())'
237 246 a1
238 247 a2
239 248 b1
240 249 b2
241 250 c1
242 251
243 252 Test files properties
244 253
245 254 >>> open('bin', 'wb').write(b'\0a') and None
246 255 $ fileset 'binary()'
247 256 bin
248 257 $ fileset 'binary() and unknown()'
249 258 bin
250 259 $ echo '^bin$' >> .hgignore
251 260 $ fileset 'binary() and ignored()'
252 261 bin
253 262 $ hg add bin
254 263 $ fileset 'binary()'
255 264 bin
256 265
257 266 $ fileset 'grep("b{1}")'
258 267 .hgignore
259 268 b1
260 269 b2
261 270 c1
262 271 $ fileset 'grep("missingparens(")'
263 272 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
264 273 [255]
265 274
266 275 #if execbit
267 276 $ chmod +x b2
268 277 $ fileset 'exec()'
269 278 b2
270 279 #endif
271 280
272 281 #if symlink
273 282 $ ln -s b2 b2link
274 283 $ fileset 'symlink() and unknown()'
275 284 b2link
276 285 $ hg add b2link
277 286 #endif
278 287
279 288 #if no-windows
280 289 $ echo foo > con.xml
281 290 $ fileset 'not portable()'
282 291 con.xml
283 292 $ hg --config ui.portablefilenames=ignore add con.xml
284 293 #endif
285 294
286 295 >>> open('1k', 'wb').write(b' '*1024) and None
287 296 >>> open('2k', 'wb').write(b' '*2048) and None
288 297 $ hg add 1k 2k
289 298 $ fileset 'size("bar")'
290 299 hg: parse error: couldn't parse size: bar
291 300 [255]
292 301 $ fileset '(1k, 2k)'
293 302 hg: parse error: can't use a list in this context
294 303 (see hg help "filesets.x or y")
295 304 [255]
296 305 $ fileset 'size(1k)'
297 306 1k
298 307 $ fileset '(1k or 2k) and size("< 2k")'
299 308 1k
300 309 $ fileset '(1k or 2k) and size("<=2k")'
301 310 1k
302 311 2k
303 312 $ fileset '(1k or 2k) and size("> 1k")'
304 313 2k
305 314 $ fileset '(1k or 2k) and size(">=1K")'
306 315 1k
307 316 2k
308 317 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
309 318 1k
310 319 $ fileset 'size("1M")'
311 320 $ fileset 'size("1 GB")'
312 321
313 322 Test merge states
314 323
315 324 $ hg ci -m manychanges
316 325 $ hg file -r . 'set:copied() & modified()'
317 326 [1]
318 327 $ hg up -C 0
319 328 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
320 329 $ echo c >> b2
321 330 $ hg ci -m diverging b2
322 331 created new head
323 332 $ fileset 'resolved()'
324 333 $ fileset 'unresolved()'
325 334 $ hg merge
326 335 merging b2
327 336 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
328 337 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
329 338 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
330 339 [1]
331 340 $ fileset 'resolved()'
332 341 $ fileset 'unresolved()'
333 342 b2
334 343 $ echo e > b2
335 344 $ hg resolve -m b2
336 345 (no more unresolved files)
337 346 $ fileset 'resolved()'
338 347 b2
339 348 $ fileset 'unresolved()'
340 349 $ hg ci -m merge
341 350
342 351 Test subrepo predicate
343 352
344 353 $ hg init sub
345 354 $ echo a > sub/suba
346 355 $ hg -R sub add sub/suba
347 356 $ hg -R sub ci -m sub
348 357 $ echo 'sub = sub' > .hgsub
349 358 $ hg init sub2
350 359 $ echo b > sub2/b
351 360 $ hg -R sub2 ci -Am sub2
352 361 adding b
353 362 $ echo 'sub2 = sub2' >> .hgsub
354 363 $ fileset 'subrepo()'
355 364 $ hg add .hgsub
356 365 $ fileset 'subrepo()'
357 366 sub
358 367 sub2
359 368 $ fileset 'subrepo("sub")'
360 369 sub
361 370 $ fileset 'subrepo("glob:*")'
362 371 sub
363 372 sub2
364 373 $ hg ci -m subrepo
365 374
366 375 Test that .hgsubstate is updated as appropriate during a conversion. The
367 376 saverev property is enough to alter the hashes of the subrepo.
368 377
369 378 $ hg init ../converted
370 379 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
371 380 > sub ../converted/sub
372 381 initializing destination ../converted/sub repository
373 382 scanning source...
374 383 sorting...
375 384 converting...
376 385 0 sub
377 386 $ hg clone -U sub2 ../converted/sub2
378 387 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
379 388 > . ../converted
380 389 scanning source...
381 390 sorting...
382 391 converting...
383 392 4 addfiles
384 393 3 manychanges
385 394 2 diverging
386 395 1 merge
387 396 0 subrepo
388 397 no ".hgsubstate" updates will be made for "sub2"
389 398 $ hg up -q -R ../converted -r tip
390 399 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
391 400 a
392 401 b
393 402 $ oldnode=`hg log -r tip -T "{node}\n"`
394 403 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
395 404 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
396 405
397 406 Test with a revision
398 407
399 408 $ hg log -G --template '{rev} {desc}\n'
400 409 @ 4 subrepo
401 410 |
402 411 o 3 merge
403 412 |\
404 413 | o 2 diverging
405 414 | |
406 415 o | 1 manychanges
407 416 |/
408 417 o 0 addfiles
409 418
410 419 $ echo unknown > unknown
411 420 $ fileset -r1 'modified()'
412 421 b2
413 422 $ fileset -r1 'added() and c1'
414 423 c1
415 424 $ fileset -r1 'removed()'
416 425 a2
417 426 $ fileset -r1 'deleted()'
418 427 $ fileset -r1 'unknown()'
419 428 $ fileset -r1 'ignored()'
420 429 $ fileset -r1 'hgignore()'
421 430 .hgignore
422 431 a2
423 432 b2
424 433 bin
425 434 c2
426 435 sub2
427 436 $ fileset -r1 'binary()'
428 437 bin
429 438 $ fileset -r1 'size(1k)'
430 439 1k
431 440 $ fileset -r3 'resolved()'
432 441 $ fileset -r3 'unresolved()'
433 442
434 443 #if execbit
435 444 $ fileset -r1 'exec()'
436 445 b2
437 446 #endif
438 447
439 448 #if symlink
440 449 $ fileset -r1 'symlink()'
441 450 b2link
442 451 #endif
443 452
444 453 #if no-windows
445 454 $ fileset -r1 'not portable()'
446 455 con.xml
447 456 $ hg forget 'con.xml'
448 457 #endif
449 458
450 459 $ fileset -r4 'subrepo("re:su.*")'
451 460 sub
452 461 sub2
453 462 $ fileset -r4 'subrepo(re:su.*)'
454 463 sub
455 464 sub2
456 465 $ fileset -r4 'subrepo("sub")'
457 466 sub
458 467 $ fileset -r4 'b2 or c1'
459 468 b2
460 469 c1
461 470
462 471 >>> open('dos', 'wb').write(b"dos\r\n") and None
463 472 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
464 473 >>> open('mac', 'wb').write(b"mac\r") and None
465 474 $ hg add dos mixed mac
466 475
467 476 (remove a1, to examine safety of 'eol' on removed files)
468 477 $ rm a1
469 478
470 479 $ fileset 'eol(dos)'
471 480 dos
472 481 mixed
473 482 $ fileset 'eol(unix)'
474 483 .hgignore
475 484 .hgsub
476 485 .hgsubstate
477 486 b1
478 487 b2
479 488 b2.orig
480 489 c1
481 490 c2
482 491 c3
483 492 con.xml (no-windows !)
484 493 mixed
485 494 unknown
486 495 $ fileset 'eol(mac)'
487 496 mac
488 497
489 498 Test safety of 'encoding' on removed files
490 499
491 500 $ fileset 'encoding("ascii")'
492 501 .hgignore
493 502 .hgsub
494 503 .hgsubstate
495 504 1k
496 505 2k
497 506 b1
498 507 b2
499 508 b2.orig
500 509 b2link (symlink !)
501 510 bin
502 511 c1
503 512 c2
504 513 c3
505 514 con.xml (no-windows !)
506 515 dos
507 516 mac
508 517 mixed
509 518 unknown
510 519
511 520 Test 'revs(...)'
512 521 ================
513 522
514 523 small reminder of the repository state
515 524
516 525 $ hg log -G
517 526 @ changeset: 4:* (glob)
518 527 | tag: tip
519 528 | user: test
520 529 | date: Thu Jan 01 00:00:00 1970 +0000
521 530 | summary: subrepo
522 531 |
523 532 o changeset: 3:* (glob)
524 533 |\ parent: 2:55b05bdebf36
525 534 | | parent: 1:* (glob)
526 535 | | user: test
527 536 | | date: Thu Jan 01 00:00:00 1970 +0000
528 537 | | summary: merge
529 538 | |
530 539 | o changeset: 2:55b05bdebf36
531 540 | | parent: 0:8a9576c51c1f
532 541 | | user: test
533 542 | | date: Thu Jan 01 00:00:00 1970 +0000
534 543 | | summary: diverging
535 544 | |
536 545 o | changeset: 1:* (glob)
537 546 |/ user: test
538 547 | date: Thu Jan 01 00:00:00 1970 +0000
539 548 | summary: manychanges
540 549 |
541 550 o changeset: 0:8a9576c51c1f
542 551 user: test
543 552 date: Thu Jan 01 00:00:00 1970 +0000
544 553 summary: addfiles
545 554
546 555 $ hg status --change 0
547 556 A a1
548 557 A a2
549 558 A b1
550 559 A b2
551 560 $ hg status --change 1
552 561 M b2
553 562 A 1k
554 563 A 2k
555 564 A b2link (no-windows !)
556 565 A bin
557 566 A c1
558 567 A con.xml (no-windows !)
559 568 R a2
560 569 $ hg status --change 2
561 570 M b2
562 571 $ hg status --change 3
563 572 M b2
564 573 A 1k
565 574 A 2k
566 575 A b2link (no-windows !)
567 576 A bin
568 577 A c1
569 578 A con.xml (no-windows !)
570 579 R a2
571 580 $ hg status --change 4
572 581 A .hgsub
573 582 A .hgsubstate
574 583 $ hg status
575 584 A dos
576 585 A mac
577 586 A mixed
578 587 R con.xml (no-windows !)
579 588 ! a1
580 589 ? b2.orig
581 590 ? c3
582 591 ? unknown
583 592
584 593 Test files at -r0 should be filtered by files at wdir
585 594 -----------------------------------------------------
586 595
587 596 $ fileset -r0 'tracked() and revs("wdir()", tracked())'
588 597 a1
589 598 b1
590 599 b2
591 600
592 601 Test that "revs()" work at all
593 602 ------------------------------
594 603
595 604 $ fileset "revs('2', modified())"
596 605 b2
597 606
598 607 Test that "revs()" work for file missing in the working copy/current context
599 608 ----------------------------------------------------------------------------
600 609
601 610 (a2 not in working copy)
602 611
603 612 $ fileset "revs('0', added())"
604 613 a1
605 614 a2
606 615 b1
607 616 b2
608 617
609 618 (none of the file exist in "0")
610 619
611 620 $ fileset -r 0 "revs('4', added())"
612 621 .hgsub
613 622 .hgsubstate
614 623
615 624 Call with empty revset
616 625 --------------------------
617 626
618 627 $ fileset "revs('2-2', modified())"
619 628
620 629 Call with revset matching multiple revs
621 630 ---------------------------------------
622 631
623 632 $ fileset "revs('0+4', added())"
624 633 .hgsub
625 634 .hgsubstate
626 635 a1
627 636 a2
628 637 b1
629 638 b2
630 639
631 640 overlapping set
632 641
633 642 $ fileset "revs('1+2', modified())"
634 643 b2
635 644
636 645 test 'status(...)'
637 646 =================
638 647
639 648 Simple case
640 649 -----------
641 650
642 651 $ fileset "status(3, 4, added())"
643 652 .hgsub
644 653 .hgsubstate
645 654
646 655 use rev to restrict matched file
647 656 -----------------------------------------
648 657
649 658 $ hg status --removed --rev 0 --rev 1
650 659 R a2
651 660 $ fileset "status(0, 1, removed())"
652 661 a2
653 662 $ fileset "tracked() and status(0, 1, removed())"
654 663 $ fileset -r 4 "status(0, 1, removed())"
655 664 a2
656 665 $ fileset -r 4 "tracked() and status(0, 1, removed())"
657 666 $ fileset "revs('4', tracked() and status(0, 1, removed()))"
658 667 $ fileset "revs('0', tracked() and status(0, 1, removed()))"
659 668 a2
660 669
661 670 check wdir()
662 671 ------------
663 672
664 673 $ hg status --removed --rev 4
665 674 R con.xml (no-windows !)
666 675 $ fileset "status(4, 'wdir()', removed())"
667 676 con.xml (no-windows !)
668 677
669 678 $ hg status --removed --rev 2
670 679 R a2
671 680 $ fileset "status('2', 'wdir()', removed())"
672 681 a2
673 682
674 683 test backward status
675 684 --------------------
676 685
677 686 $ hg status --removed --rev 0 --rev 4
678 687 R a2
679 688 $ hg status --added --rev 4 --rev 0
680 689 A a2
681 690 $ fileset "status(4, 0, added())"
682 691 a2
683 692
684 693 test cross branch status
685 694 ------------------------
686 695
687 696 $ hg status --added --rev 1 --rev 2
688 697 A a2
689 698 $ fileset "status(1, 2, added())"
690 699 a2
691 700
692 701 test with multi revs revset
693 702 ---------------------------
694 703 $ hg status --added --rev 0:1 --rev 3:4
695 704 A .hgsub
696 705 A .hgsubstate
697 706 A 1k
698 707 A 2k
699 708 A b2link (no-windows !)
700 709 A bin
701 710 A c1
702 711 A con.xml (no-windows !)
703 712 $ fileset "status('0:1', '3:4', added())"
704 713 .hgsub
705 714 .hgsubstate
706 715 1k
707 716 2k
708 717 b2link (no-windows !)
709 718 bin
710 719 c1
711 720 con.xml (no-windows !)
712 721
713 722 tests with empty value
714 723 ----------------------
715 724
716 725 Fully empty revset
717 726
718 727 $ fileset "status('', '4', added())"
719 728 hg: parse error: first argument to status must be a revision
720 729 [255]
721 730 $ fileset "status('2', '', added())"
722 731 hg: parse error: second argument to status must be a revision
723 732 [255]
724 733
725 734 Empty revset will error at the revset layer
726 735
727 736 $ fileset "status(' ', '4', added())"
728 737 hg: parse error at 1: not a prefix: end
729 738 (
730 739 ^ here)
731 740 [255]
732 741 $ fileset "status('2', ' ', added())"
733 742 hg: parse error at 1: not a prefix: end
734 743 (
735 744 ^ here)
736 745 [255]
General Comments 0
You need to be logged in to leave comments. Login now