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